19
19
20
20
package org.ossreviewtoolkit.analyzer.managers
21
21
22
- import com.vdurmont.semver4j.Requirement
23
-
24
22
import java.io.File
25
23
import java.util.SortedSet
26
24
@@ -44,76 +42,14 @@ import org.ossreviewtoolkit.model.config.PackageManagerConfiguration
44
42
import org.ossreviewtoolkit.model.config.RepositoryConfiguration
45
43
import org.ossreviewtoolkit.utils.common.CommandLineTool
46
44
import org.ossreviewtoolkit.utils.common.Os
47
- import org.ossreviewtoolkit.utils.common.ProcessCapture
48
45
import org.ossreviewtoolkit.utils.common.collectMessages
49
46
import org.ossreviewtoolkit.utils.common.safeDeleteRecursively
50
- import org.ossreviewtoolkit.utils.ort.createOrtTempDir
51
- import org.ossreviewtoolkit.utils.ort.createOrtTempFile
52
47
import org.ossreviewtoolkit.utils.ort.showStackTrace
53
48
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 {
71
50
override fun command (workingDir : File ? ) = if (Os .isWindows) " py" else " python3"
72
51
73
52
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
- }
117
53
}
118
54
119
55
private const val OPTION_OPERATING_SYSTEM = " operatingSystem"
@@ -173,52 +109,23 @@ class Pip(
173
109
174
110
override fun transformVersion (output : String ) = output.removePrefix(" pip " ).substringBefore(' ' )
175
111
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
-
195
112
override fun resolveDependencies (definitionFile : File , labels : Map <String , String >): List <ProjectAnalyzerResult > {
196
113
// For an overview, dependency resolution involves the following steps:
197
114
// 1. Get metadata about the local project via `python setup.py`.
198
115
// 2. Get the dependency tree and dependency metadata via python-inspector.
199
116
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)
208
118
val (packages, installDependencies) = getInstallDependencies(definitionFile)
209
119
210
120
// TODO: Handle "extras" and "tests" dependencies.
211
121
val scopes = sortedSetOf(
212
122
Scope (" install" , installDependencies)
213
123
)
214
124
215
- // Remove the virtualenv by simply deleting the directory.
216
- virtualEnvDir.safeDeleteRecursively()
217
-
218
125
return listOf (ProjectAnalyzerResult (project.copy(scopeDependencies = scopes), packages))
219
126
}
220
127
221
- private fun getProjectBasics (definitionFile : File , virtualEnvDir : File ): Project {
128
+ private fun getProjectBasics (definitionFile : File ): Project {
222
129
val authors = sortedSetOf<String >()
223
130
val declaredLicenses = sortedSetOf<String >()
224
131
@@ -228,7 +135,7 @@ class Pip(
228
135
val (setupName, setupVersion, setupHomepage) = if (workingDir.resolve(" setup.py" ).isFile) {
229
136
// See https://docs.python.org/3.8/distutils/setupscript.html#additional-meta-data.
230
137
fun getSetupPyMetadata (option : String ): String? {
231
- val process = runInVirtualEnv(virtualEnvDir, workingDir, " python " , " setup.py" , option)
138
+ val process = Python . run ( workingDir, " setup.py" , option)
232
139
val metadata = process.stdout.trim()
233
140
return metadata.takeUnless { process.isError || metadata == " UNKNOWN" }
234
141
}
@@ -362,18 +269,4 @@ class Pip(
362
269
val license = classifiers.takeIf { it.first() in licenseClassifiers }?.last()
363
270
return license?.takeUnless { it in licenseClassifiers }
364
271
}
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
- }
379
272
}
0 commit comments