import * as exec from 'child_process' ; import { ComponentDetails } from 'jfrog-client-js' ; import * as path from 'path' ; import * as Collections from 'typescript-collections' ; import * as vscode from 'vscode' ; import { LogManager } from '../log/logManager' ; import { PypiTreeNode } from '../treeDataProviders/dependenciesTree/dependenciesRoot/pypiTree' ; import { DependenciesTreeNode } from '../treeDataProviders/dependenciesTree/dependenciesTreeNode' ; import { TreesManager } from '../treeDataProviders/treesManager' ; import { Configuration } from './configuration' ; import { ScanUtils } from './scanUtils' ; export class PypiUtils { public static readonly DOCUMENT_SELECTOR : vscode . DocumentSelector = { scheme : 'file' , pattern : '**/*requirements*.txt' } ; public static readonly PYTHON_SCRIPTS : string = path . join ( ScanUtils . RESOURCES_DIR , 'python' ) ; public static readonly PIP_DEP_TREE_SCRIPT : string = path . join ( PypiUtils . PYTHON_SCRIPTS , 'pipDepTree.py' ) ; public static readonly CHECK_VENV_SCRIPT : string = path . join ( PypiUtils . PYTHON_SCRIPTS , 'checkVenv.py' ) ; public static readonly PKG_TYPE : string = 'pypi' ; param document public static getDependenciesPos ( document ) { let res = [ ] ; let packageJsonContent = document . getText ( ) ; let dependenciesMatch = packageJsonContent . match ( 'install_requires(s*)=' ) ; if ( ! dependenciesMatch ) { return res ; } res . push ( document . positionAt ( < number > dependenciesMatch . index ) ) ; res . push ( new vscode . Position ( res [ 0 ] . line , res [ 0 ] . character + dependenciesMatch [ 0 ] . length ) ) ; return res ; } param document param requirementsContent param dependenciesTreeNode public static getDependencyPos ( document , requirementsContent , dependenciesTreeNode ) { let res = [ ] ; let dependencyMatch = requirementsContent . match ( dependenciesTreeNode . generalInfo . artifactId ) ; if ( ! dependencyMatch ) { return res ; } res . push ( document . positionAt ( < number > dependencyMatch . index ) ) ; res . push ( new vscode . Position ( res [ 0 ] . line , res [ 0 ] . character + dependencyMatch [ 0 ] . length ) ) ; return res ; } param workspaceFolders param logManager public static async arePythonFilesExist ( workspaceFolder , logManager ) { logManager . logMessage ( 'Locating python files in workspace "' + workspaceFolder . name + '".' , 'INFO' ) ; let wsPythonFiles = await vscode . workspace . findFiles ( { base : workspaceFolder . uri . fsPath , pattern : '**/*{setup.py,requirements*.txt}' } , Configuration . getScanExcludePattern ( workspaceFolder ) ) ; if ( logManager && wsPythonFiles . length > 0 ) { logManager . logMessage ( 'Detected python files in workspace ' + workspaceFolder . name + ': [' + wsPythonFiles . toString ( ) + ']' , 'DEBUG' ) ; } return Promise . resolve ( wsPythonFiles . length > 0 ) ; } param pythonFiles param workspaceFolders param componentsToScan param treesManager param parent param quickScan public static async createDependenciesTrees ( pythonFiles , workspaceFolders , componentsToScan , treesManager , parent , quickScan ) { if ( ! pythonFiles ) { treesManager . logManager . logMessage ( 'No setup.py and requirements files found in workspaces.' , 'DEBUG' ) ; return ; } let pythonExtension ; for ( let workspaceFolder of workspaceFolders ) { let pythonFilesExist = await PypiUtils . arePythonFilesExist ( workspaceFolder , treesManager . logManager ) ; if ( ! pythonFilesExist ) { treesManager . logManager . logMessage ( 'No setup.py and requirements files found in workspace ' + workspaceFolder . name + '.' , 'DEBUG' ) ; continue ; } if ( ! pythonExtension ) { pythonExtension = await PypiUtils . getAndActivatePythonExtension ( ) ; if ( ! pythonExtension ) { vscode . window . showErrorMessage ( 'Could not scan Pypi project dependencies, because python extension is not installed. ' + 'Please install Python extension: https://marketplace.visualstudio.com/items?itemName=ms-python.python' ) ; return ; } } let pythonPath = PypiUtils . getPythonPath ( pythonExtension , workspaceFolder ) ; if ( ! pythonPath ) { vscode . window . showErrorMessage ( 'Could not scan Pypi project dependencies, because python interpreter is not set.' ) ; return ; } if ( ! PypiUtils . isInVirtualEnv ( pythonPath , workspaceFolder . uri . fsPath , treesManager . logManager ) ) { vscode . window . showErrorMessage ( 'Please install and activate a virtual environment before running Xray scan. Then, install your Python project in that environment.' ) ; return ; } treesManager . logManager . logMessage ( 'Analyzing setup.py and requirements files of ' + workspaceFolder . name , 'INFO' ) ; let dependenciesTreeNode = new PypiTreeNode ( workspaceFolder . uri . fsPath , componentsToScan , treesManager , pythonPath , parent ) ; dependenciesTreeNode . refreshDependencies ( quickScan ) ; } } /**
     * Return reference to the VS-Code Python extension if installed.
     * Activate it to allow tracking on environmental changes -
     * If virtual env is not installed, we want that the Python extension will detect new virtualenv environments and suggest activation.
     */ public static async getAndActivatePythonExtension ( ) { let pythonExtension = vscode . extensions . getExtension ( 'ms-python.python' ) ; if ( ! pythonExtension ) { return ; } if ( ! pythonExtension . isActive ) { await pythonExtension . activate ( ) ; } return pythonExtension ; } param pythonExtension param workspaceFolder private static getPythonPath ( pythonExtension , workspaceFolder ) { let executionDetails = pythonExtension ?. exports . settings . getExecutionDetails ( workspaceFolder . uri ) ; let execCommand = executionDetails ?. execCommand ; return execCommand ? execCommand [ 0 ] : undefined ; } param pythonPath param workspaceFolder public static isInVirtualEnv ( pythonPath , workspaceFolder , logManager ) { try { exec . execSync ( pythonPath + ' ' + PypiUtils . CHECK_VENV_SCRIPT , { cwd : workspaceFolder } as exec . ExecSyncOptionsWithStringEncoding ) ; return true ; } catch ( error ) { logManager . logError ( < any > error , false ) ; return false ; } } param fsPath public static async getRequirementsFiles ( fsPath ) { return await vscode . workspace . findFiles ( { base : fsPath , pattern : '**/*requirements*.txt' } , Configuration . getScanExcludePattern ( ) ) ; } }