Skip to content

Commit f45757d

Browse files
authored
Merge pull request #1408 from blackducksoftware/uv-lockfile-detector
UV Lockfile Detector
2 parents a19b5a4 + a9325d2 commit f45757d

File tree

14 files changed

+459
-34
lines changed

14 files changed

+459
-34
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.blackduck.integration.detectable.detectable.result;
2+
3+
public class UVLockfileNotFoundDetectableResult extends FailedDetectableResult {
4+
private final String directoryPath;
5+
6+
public UVLockfileNotFoundDetectableResult(String directoryPath) {
7+
this.directoryPath = directoryPath;
8+
}
9+
10+
@Override
11+
public String toDescription() {
12+
return String.format(
13+
"A pyproject.toml was located in %s, but the uv.lock or requirements.txt file was NOT located. Please check your configuration and try again.",
14+
directoryPath
15+
);
16+
}
17+
}

detectable/src/main/java/com/blackduck/integration/detectable/detectables/uv/UVDetectorOptions.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
package com.blackduck.integration.detectable.detectables.uv;
22

33

4-
import java.util.List;
54
import java.util.HashSet;
5+
import java.util.List;
66
import java.util.Set;
77
import java.util.stream.Collectors;
88

detectable/src/main/java/com/blackduck/integration/detectable/detectables/uv/buildexe/UVBuildDetectable.java

-6
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,8 @@
1818
import com.blackduck.integration.detectable.extraction.Extraction;
1919
import com.blackduck.integration.detectable.extraction.ExtractionEnvironment;
2020
import com.blackduck.integration.executable.ExecutableRunnerException;
21-
import org.apache.commons.io.FileUtils;
22-
import org.slf4j.Logger;
23-
import org.slf4j.LoggerFactory;
24-
import org.tomlj.internal.TomlParser;
2521

2622
import java.io.File;
27-
import java.nio.charset.StandardCharsets;
28-
import java.util.List;
2923

3024
@DetectableInfo(name = "UV Build", language = "Python", forge = "PyPI", accuracy = DetectableAccuracyType.HIGH, requirementsMarkdown = "File: pyproject.toml file. Executable: uv.")
3125
public class UVBuildDetectable extends Detectable {

detectable/src/main/java/com/blackduck/integration/detectable/detectables/uv/buildexe/UVBuildExtractor.java

-2
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,10 @@
1111
import com.blackduck.integration.executable.ExecutableOutput;
1212
import com.blackduck.integration.executable.ExecutableRunnerException;
1313
import com.blackduck.integration.util.NameVersion;
14-
import org.apache.commons.io.FileUtils;
1514
import org.slf4j.Logger;
1615
import org.slf4j.LoggerFactory;
1716

1817
import java.io.File;
19-
import java.nio.charset.StandardCharsets;
2018
import java.util.ArrayList;
2119
import java.util.List;
2220
import java.util.Optional;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package com.blackduck.integration.detectable.detectables.uv.lockfile;
2+
3+
import com.blackduck.integration.common.util.finder.FileFinder;
4+
import com.blackduck.integration.detectable.Detectable;
5+
import com.blackduck.integration.detectable.DetectableEnvironment;
6+
import com.blackduck.integration.detectable.detectable.DetectableAccuracyType;
7+
import com.blackduck.integration.detectable.detectable.Requirements;
8+
import com.blackduck.integration.detectable.detectable.annotation.DetectableInfo;
9+
import com.blackduck.integration.detectable.detectable.exception.DetectableException;
10+
import com.blackduck.integration.detectable.detectable.result.DetectableResult;
11+
import com.blackduck.integration.detectable.detectable.result.FailedDetectableResult;
12+
import com.blackduck.integration.detectable.detectable.result.PassedDetectableResult;
13+
import com.blackduck.integration.detectable.detectable.result.UVLockfileNotFoundDetectableResult;
14+
import com.blackduck.integration.detectable.detectables.uv.UVDetectorOptions;
15+
import com.blackduck.integration.detectable.detectables.uv.parse.UVTomlParser;
16+
import com.blackduck.integration.detectable.extraction.Extraction;
17+
import com.blackduck.integration.detectable.extraction.ExtractionEnvironment;
18+
import com.blackduck.integration.executable.ExecutableRunnerException;
19+
import org.slf4j.Logger;
20+
import org.slf4j.LoggerFactory;
21+
22+
import java.io.File;
23+
import java.io.IOException;
24+
25+
@DetectableInfo(name = "UV Lockfile", language = "Python", forge = "PyPi", accuracy = DetectableAccuracyType.HIGH, requirementsMarkdown = "File: pyproject.toml file. Lock File: uv.lock or requirements.txt file.")
26+
public class UVLockFileDetectable extends Detectable {
27+
28+
private final Logger logger = LoggerFactory.getLogger(this.getClass());
29+
private static final String PYPROJECT_TOML = "pyproject.toml";
30+
private static final String UV_LOCK_FILE = "uv.lock";
31+
private static final String REQUIREMENTS_TXT = "requirements.txt";
32+
private final FileFinder fileFinder;
33+
private final UVDetectorOptions uvDetectorOptions;
34+
private File uvLockFile;
35+
private File requirementsTxtFile;
36+
private File uvTomlFile;
37+
private UVTomlParser uvTomlParser;
38+
private final UVLockfileExtractor uvLockfileExtractor;
39+
40+
public UVLockFileDetectable(DetectableEnvironment environment, FileFinder fileFinder, UVDetectorOptions uvDetectorOptions, UVLockfileExtractor uvLockfileExtractor) {
41+
super(environment);
42+
this.fileFinder = fileFinder;
43+
this.uvDetectorOptions = uvDetectorOptions;
44+
this.uvLockfileExtractor = uvLockfileExtractor;
45+
}
46+
47+
@Override
48+
public DetectableResult applicable() {
49+
Requirements requirements = new Requirements(fileFinder, environment);
50+
uvTomlFile = requirements.file(PYPROJECT_TOML);
51+
52+
uvLockFile = fileFinder.findFile(environment.getDirectory(), UV_LOCK_FILE);
53+
requirementsTxtFile = fileFinder.findFile(environment.getDirectory(), REQUIREMENTS_TXT);
54+
55+
// check [tool.uv] managed setting and if set to false, skip this detector
56+
if(uvTomlFile != null) {
57+
uvTomlParser = new UVTomlParser(uvTomlFile);
58+
if(!uvTomlParser.parseManagedKey()) {
59+
return new FailedDetectableResult();
60+
}
61+
}
62+
63+
return requirements.result();
64+
}
65+
66+
@Override
67+
public DetectableResult extractable() throws DetectableException {
68+
if(uvLockFile == null && requirementsTxtFile == null && uvTomlFile != null) {
69+
return new UVLockfileNotFoundDetectableResult(environment.getDirectory().getAbsolutePath());
70+
}
71+
72+
return new PassedDetectableResult();
73+
}
74+
75+
@Override
76+
public Extraction extract(ExtractionEnvironment extractionEnvironment) throws ExecutableRunnerException, IOException {
77+
return uvLockfileExtractor.extract(uvDetectorOptions, uvTomlParser, uvLockFile, requirementsTxtFile);
78+
}
79+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package com.blackduck.integration.detectable.detectables.uv.lockfile;
2+
3+
import com.blackduck.integration.bdio.graph.DependencyGraph;
4+
import com.blackduck.integration.detectable.detectable.codelocation.CodeLocation;
5+
import com.blackduck.integration.detectable.detectables.pip.parser.RequirementsFileDependencyTransformer;
6+
import com.blackduck.integration.detectable.detectables.uv.UVDetectorOptions;
7+
import com.blackduck.integration.detectable.detectables.uv.parse.UVTomlParser;
8+
import com.blackduck.integration.detectable.detectables.uv.transform.UVLockParser;
9+
import com.blackduck.integration.detectable.extraction.Extraction;
10+
import com.blackduck.integration.detectable.python.util.PythonDependency;
11+
import com.blackduck.integration.detectable.python.util.PythonDependencyTransformer;
12+
import com.blackduck.integration.executable.ExecutableRunnerException;
13+
import com.blackduck.integration.util.NameVersion;
14+
import org.apache.commons.io.FileUtils;
15+
16+
import java.io.File;
17+
import java.io.IOException;
18+
import java.nio.charset.StandardCharsets;
19+
import java.util.ArrayList;
20+
import java.util.List;
21+
import java.util.Optional;
22+
23+
public class UVLockfileExtractor {
24+
25+
private final UVLockParser uvLockParser;
26+
private final PythonDependencyTransformer requirementsFileTransformer;
27+
private final RequirementsFileDependencyTransformer requirementsFileDependencyTransformer;
28+
29+
public UVLockfileExtractor(UVLockParser uvLockParser, PythonDependencyTransformer requirementsFileTransformer, RequirementsFileDependencyTransformer requirementsFileDependencyTransformer) {
30+
this.uvLockParser = uvLockParser;
31+
this.requirementsFileTransformer = requirementsFileTransformer;
32+
this.requirementsFileDependencyTransformer = requirementsFileDependencyTransformer;
33+
}
34+
35+
public Extraction extract(UVDetectorOptions uvDetectorOptions, UVTomlParser uvTomlParser, File uvLockFile, File requirementsTxtFile) throws ExecutableRunnerException, IOException {
36+
try {
37+
Optional<NameVersion> projectNameVersion = uvTomlParser.parseNameVersion();
38+
String projectName = uvTomlParser.getProjectName(); // get just project name in case version doesn't exist
39+
40+
List<CodeLocation> codeLocations = new ArrayList<>();
41+
if (uvLockFile != null) {
42+
String uvLockContents = FileUtils.readFileToString(uvLockFile, StandardCharsets.UTF_8);
43+
codeLocations = uvLockParser.parseLockFile(uvLockContents, projectName, uvDetectorOptions);
44+
}
45+
46+
if (requirementsTxtFile != null) {
47+
List<PythonDependency> dependencies = requirementsFileTransformer.transform(requirementsTxtFile);
48+
DependencyGraph dependencyGraph = requirementsFileDependencyTransformer.transform(dependencies);
49+
CodeLocation codeLocation = new CodeLocation(dependencyGraph);
50+
codeLocations.add(codeLocation);
51+
}
52+
53+
54+
return new Extraction.Builder()
55+
.success(codeLocations)
56+
.nameVersionIfPresent(projectNameVersion)
57+
.build();
58+
} catch (Exception e) {
59+
return new Extraction.Builder().exception(e).build();
60+
}
61+
}
62+
63+
}
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,17 @@
11
package com.blackduck.integration.detectable.detectables.uv.parse;
22

3-
import java.io.File;
4-
import java.nio.charset.StandardCharsets;
5-
import java.util.Optional;
6-
3+
import com.blackduck.integration.util.NameVersion;
74
import org.apache.commons.io.FileUtils;
85
import org.slf4j.Logger;
96
import org.slf4j.LoggerFactory;
107
import org.tomlj.Toml;
118
import org.tomlj.TomlParseResult;
12-
13-
import com.blackduck.integration.util.NameVersion;
149
import org.tomlj.TomlTable;
1510

11+
import java.io.File;
12+
import java.nio.charset.StandardCharsets;
13+
import java.util.Optional;
14+
1615
public class UVTomlParser {
1716

1817
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@@ -24,26 +23,40 @@ public class UVTomlParser {
2423
private static final String MANAGED_KEY = "managed";
2524
private static final String UV_TOOL_KEY = "tool.uv";
2625

26+
private String projectName = "uvProject";
27+
28+
private final File uvTomlFile;
2729
private TomlParseResult uvToml;
2830

2931
public UVTomlParser(File uvTomlFile) {
30-
parseUVToml(uvTomlFile);
32+
this.uvTomlFile = uvTomlFile;
3133
}
3234

3335
public Optional<NameVersion> parseNameVersion() {
34-
if (uvToml.contains(PROJECT_KEY)) {
35-
return Optional.ofNullable(uvToml.getTable(PROJECT_KEY))
36-
.filter(info -> info.contains(NAME_KEY))
37-
.filter(info -> info.contains(VERSION_KEY))
38-
.map(info -> new NameVersion(info.getString(NAME_KEY), info.getString(VERSION_KEY)));
36+
if (uvToml != null && uvToml.contains(PROJECT_KEY)) {
37+
38+
TomlTable projectTable = uvToml.getTable(PROJECT_KEY);
39+
if(projectTable.contains(NAME_KEY)) {
40+
projectName = projectTable.getString(NAME_KEY);
41+
} else {
42+
return Optional.empty();
43+
}
44+
45+
if(projectTable.contains(VERSION_KEY)) {
46+
String version = projectTable.getString(VERSION_KEY);
47+
return Optional.of(new NameVersion(projectName, version));
48+
} else {
49+
return Optional.empty();
50+
}
3951
}
4052
return Optional.empty();
4153
}
4254

4355

4456
// check [tool.uv] managed setting
4557
public boolean parseManagedKey() {
46-
if (uvToml.contains(UV_TOOL_KEY)) {
58+
parseUVToml();
59+
if (uvToml != null && uvToml.contains(UV_TOOL_KEY)) {
4760
TomlTable uvToolTable = uvToml.getTable(UV_TOOL_KEY);
4861
if (uvToolTable.contains(MANAGED_KEY)) {
4962
return uvToolTable.getBoolean(MANAGED_KEY);
@@ -53,7 +66,7 @@ public boolean parseManagedKey() {
5366
return true;
5467
}
5568

56-
public void parseUVToml(File uvTomlFile) {
69+
public void parseUVToml() {
5770
try {
5871
String uvTomlContents = FileUtils.readFileToString(uvTomlFile, StandardCharsets.UTF_8);
5972
uvToml = Toml.parse(uvTomlContents);
@@ -62,4 +75,7 @@ public void parseUVToml(File uvTomlFile) {
6275
}
6376
}
6477

78+
public String getProjectName() {
79+
return projectName;
80+
}
6581
}

0 commit comments

Comments
 (0)