Skip to content

Commit 6952b0f

Browse files
committed
Pip: Do not create a virtual environment
As no dependencies are installed anymore there is no need to create a virtual environment. The Python binary from the path is now used to get metadata from setup.py. This might temporarily cause issues if the setup.py is not compatible with this Python version, but this will soon be fixed once this data can be retrieved from the python-inspector output (this will be available once [1] is released). [1]: aboutcode-org/python-inspector#69 Signed-off-by: Martin Nonnenmacher <[email protected]>
1 parent dc893ec commit 6952b0f

File tree

1 file changed

+4
-111
lines changed
  • analyzer/src/main/kotlin/managers

1 file changed

+4
-111
lines changed

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

+4-111
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,6 @@
1919

2020
package org.ossreviewtoolkit.analyzer.managers
2121

22-
import com.vdurmont.semver4j.Requirement
23-
2422
import java.io.File
2523
import java.util.SortedSet
2624

@@ -44,76 +42,14 @@ import org.ossreviewtoolkit.model.config.PackageManagerConfiguration
4442
import org.ossreviewtoolkit.model.config.RepositoryConfiguration
4543
import org.ossreviewtoolkit.utils.common.CommandLineTool
4644
import org.ossreviewtoolkit.utils.common.Os
47-
import org.ossreviewtoolkit.utils.common.ProcessCapture
4845
import org.ossreviewtoolkit.utils.common.collectMessages
4946
import org.ossreviewtoolkit.utils.common.safeDeleteRecursively
50-
import org.ossreviewtoolkit.utils.ort.createOrtTempDir
51-
import org.ossreviewtoolkit.utils.ort.createOrtTempFile
5247
import org.ossreviewtoolkit.utils.ort.showStackTrace
5348

54-
object VirtualEnv : CommandLineTool {
55-
override fun command(workingDir: File?) = "virtualenv"
56-
57-
override fun transformVersion(output: String) =
58-
// The version string can be something like:
59-
// 16.6.1
60-
// virtualenv 20.0.14 from /usr/local/lib/python2.7/dist-packages/virtualenv/__init__.pyc
61-
output.removePrefix("virtualenv ").substringBefore(' ')
62-
63-
// Ensure a minimum version known to work. Note that virtualenv bundles a version of pip, and as of pip 20.3 a new
64-
// dependency resolver is used, see http://pyfound.blogspot.com/2020/11/pip-20-3-new-resolver.html.
65-
override fun getVersionRequirement(): Requirement = Requirement.buildIvy("[15.1,)")
66-
}
67-
68-
object PythonVersion : CommandLineTool, Logging {
69-
// To use a specific version of Python on Windows we can use the "py" command with argument "-2" or "-3", see
70-
// https://docs.python.org/3/installing/#work-with-multiple-versions-of-python-installed-in-parallel.
49+
object Python : CommandLineTool, Logging {
7150
override fun command(workingDir: File?) = if (Os.isWindows) "py" else "python3"
7251

7352
override fun transformVersion(output: String) = output.removePrefix("Python ")
74-
75-
/**
76-
* Check all Python files in [workingDir] and return which version of Python they are compatible with. If all files
77-
* are compatible with Python 3, "3" is returned. If at least one file is incompatible with Python 3, "2" is
78-
* returned.
79-
*/
80-
fun getPythonMajorVersion(workingDir: File): Int {
81-
val scriptFile = createOrtTempFile("python_compatibility", ".py")
82-
scriptFile.writeBytes(javaClass.getResource("/scripts/python_compatibility.py").readBytes())
83-
84-
try {
85-
// The helper script itself always has to be run with Python 3.
86-
val scriptCmd = if (Os.isWindows) {
87-
run("-3", scriptFile.path, "-d", workingDir.path)
88-
} else {
89-
run(scriptFile.path, "-d", workingDir.path)
90-
}
91-
92-
return scriptCmd.stdout.toInt()
93-
} finally {
94-
if (!scriptFile.delete()) {
95-
logger.warn { "Helper script file '$scriptFile' could not be deleted." }
96-
}
97-
}
98-
}
99-
100-
/**
101-
* Return the absolute path to the Python interpreter for the given [version]. This is helpful as esp. on Windows
102-
* different Python versions can be installed in arbitrary locations, and the Python executable is even usually
103-
* called the same in those locations. Return `null` if no matching Python interpreter is available.
104-
*/
105-
fun getPythonInterpreter(version: Int): String? =
106-
if (Os.isWindows) {
107-
val installedVersions = run("--list-paths").stdout
108-
val versionAndPath = installedVersions.lines().find { line ->
109-
line.startsWith(" -$version")
110-
}
111-
112-
// Parse a line like " -2.7-32 C:\Python27\python.exe".
113-
versionAndPath?.split(' ', limit = 3)?.last()?.trimStart()
114-
} else {
115-
Os.getPathFromEnvironment("python$version")?.path
116-
}
11753
}
11854

11955
private const val OPTION_OPERATING_SYSTEM = "operatingSystem"
@@ -173,52 +109,23 @@ class Pip(
173109

174110
override fun transformVersion(output: String) = output.removePrefix("pip ").substringBefore(' ')
175111

176-
private fun runInVirtualEnv(
177-
virtualEnvDir: File,
178-
workingDir: File,
179-
commandName: String,
180-
vararg commandArgs: String
181-
): ProcessCapture {
182-
val binDir = if (Os.isWindows) "Scripts" else "bin"
183-
val command = virtualEnvDir.resolve(binDir).resolve(commandName)
184-
val resolvedCommand = Os.resolveWindowsExecutable(command)?.takeIf { Os.isWindows } ?: command
185-
186-
// TODO: Maybe work around long shebang paths in generated scripts within a virtualenv by calling the Python
187-
// executable in the virtualenv directly, see https://github.com/pypa/virtualenv/issues/997.
188-
val process = ProcessCapture(workingDir, resolvedCommand.path, *commandArgs)
189-
logger.debug { process.stdout }
190-
return process
191-
}
192-
193-
override fun beforeResolution(definitionFiles: List<File>) = VirtualEnv.checkVersion()
194-
195112
override fun resolveDependencies(definitionFile: File, labels: Map<String, String>): List<ProjectAnalyzerResult> {
196113
// For an overview, dependency resolution involves the following steps:
197114
// 1. Get metadata about the local project via `python setup.py`.
198115
// 2. Get the dependency tree and dependency metadata via python-inspector.
199116

200-
val workingDir = definitionFile.parentFile
201-
202-
// Try to determine the Python version the project requires.
203-
val pythonMajorVersion = PythonVersion.getPythonMajorVersion(workingDir)
204-
205-
val virtualEnvDir = setupVirtualEnv(workingDir, pythonMajorVersion)
206-
207-
val project = getProjectBasics(definitionFile, virtualEnvDir)
117+
val project = getProjectBasics(definitionFile)
208118
val (packages, installDependencies) = getInstallDependencies(definitionFile)
209119

210120
// TODO: Handle "extras" and "tests" dependencies.
211121
val scopes = sortedSetOf(
212122
Scope("install", installDependencies)
213123
)
214124

215-
// Remove the virtualenv by simply deleting the directory.
216-
virtualEnvDir.safeDeleteRecursively()
217-
218125
return listOf(ProjectAnalyzerResult(project.copy(scopeDependencies = scopes), packages))
219126
}
220127

221-
private fun getProjectBasics(definitionFile: File, virtualEnvDir: File): Project {
128+
private fun getProjectBasics(definitionFile: File): Project {
222129
val authors = sortedSetOf<String>()
223130
val declaredLicenses = sortedSetOf<String>()
224131

@@ -228,7 +135,7 @@ class Pip(
228135
val (setupName, setupVersion, setupHomepage) = if (workingDir.resolve("setup.py").isFile) {
229136
// See https://docs.python.org/3.8/distutils/setupscript.html#additional-meta-data.
230137
fun getSetupPyMetadata(option: String): String? {
231-
val process = runInVirtualEnv(virtualEnvDir, workingDir, "python", "setup.py", option)
138+
val process = Python.run(workingDir, "setup.py", option)
232139
val metadata = process.stdout.trim()
233140
return metadata.takeUnless { process.isError || metadata == "UNKNOWN" }
234141
}
@@ -362,18 +269,4 @@ class Pip(
362269
val license = classifiers.takeIf { it.first() in licenseClassifiers }?.last()
363270
return license?.takeUnless { it in licenseClassifiers }
364271
}
365-
366-
private fun setupVirtualEnv(workingDir: File, pythonMajorVersion: Int): File {
367-
// Create an out-of-tree virtualenv.
368-
logger.info { "Creating a virtualenv for the '${workingDir.name}' project directory..." }
369-
370-
val virtualEnvDir = createOrtTempDir("${workingDir.name}-virtualenv")
371-
val pythonInterpreter = requireNotNull(PythonVersion.getPythonInterpreter(pythonMajorVersion)) {
372-
"No Python interpreter found for version $pythonMajorVersion."
373-
}
374-
375-
ProcessCapture(workingDir, "virtualenv", virtualEnvDir.path, "-p", pythonInterpreter).requireSuccess()
376-
377-
return virtualEnvDir
378-
}
379272
}

0 commit comments

Comments
 (0)