def _ReaderLoop( self ): """ Read responses from TSServer and use them to resolve the DeferredResponse instances. """ while True: self._tsserver_is_running.wait() try: message = self._ReadMessage() except ( RuntimeError, ValueError ): LOGGER.exception( 'Error while reading message from server' ) if not self._ServerIsRunning(): self._tsserver_is_running.clear() continue # We ignore events for now since we don't have a use for them. msgtype = message[ 'type' ] if msgtype == 'event': eventname = message[ 'event' ] LOGGER.info( 'Received %s event from TSServer', eventname ) continue if msgtype != 'response': LOGGER.error( 'Unsupported message type', msgtype ) continue seq = message[ 'request_seq' ] with self._pending_lock: if seq in self._pending: self._pending[ seq ].resolve( message ) del self._pending[ seq ]
def StartServer(self, request_data): with self._server_state_mutex: LOGGER.info('Starting %s: %s', self.GetServerName(), self.GetCommandLine()) self._stderr_file = utils.CreateLogfile('{}_stderr'.format( utils.MakeSafeFileNameString(self.GetServerName()))) with utils.OpenForStdHandle(self._stderr_file) as stderr: self._server_handle = utils.SafePopen(self.GetCommandLine(), stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=stderr) self._connection = (lsc.StandardIOLanguageServerConnection( self._server_handle.stdin, self._server_handle.stdout, self.GetDefaultNotificationHandler())) self._connection.Start() try: self._connection.AwaitServerConnection() except lsc.LanguageServerConnectionTimeout: LOGGER.error( '%s failed to start, or did not connect successfully', self.GetServerName()) self.Shutdown() return False LOGGER.info('%s started', self.GetServerName()) return True
def GetClangdExecutableAndResourceDir( user_options ): """Return the Clangd binary from the path specified in the 'clangd_binary_path' option. Let the binary find its resource directory in that case. If no binary is found or if it's out-of-date, return nothing. If 'clangd_binary_path' is empty, return the third-party Clangd and its resource directory if the user downloaded it and if it's up to date. Otherwise, return nothing.""" clangd = user_options[ 'clangd_binary_path' ] resource_dir = None if clangd: clangd = FindExecutable( ExpandVariablesInPath( clangd ) ) if not clangd: LOGGER.error( 'No Clangd executable found at %s', user_options[ 'clangd_binary_path' ] ) return None, None if not CheckClangdVersion( clangd ): LOGGER.error( 'Clangd at %s is out-of-date', clangd ) return None, None # Try looking for the pre-built binary. else: third_party_clangd = GetThirdPartyClangd() if not third_party_clangd: return None, None clangd = third_party_clangd resource_dir = CLANG_RESOURCE_DIR LOGGER.info( 'Using Clangd from %s', clangd ) return clangd, resource_dir
def _GetRAVersion( ra_path ): ra_version = _GetCommandOutput( [ ra_path, '--version' ] ) match = RA_VERSION_REGEX.match( ra_version ) if not match: LOGGER.error( 'Cannot parse Rust Language Server version: %s', ra_version ) return None return match.group( 'version' )
def StartServer(self, request_data): with self._server_state_mutex: if self.ServerIsHealthy(): return # Ensure we cleanup all states. self._Reset() LOGGER.info('Starting clangd: %s', self._clangd_command) self._stderr_file = utils.CreateLogfile('clangd_stderr') with utils.OpenForStdHandle(self._stderr_file) as stderr: self._server_handle = utils.SafePopen(self._clangd_command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=stderr) self._connection = ( language_server_completer.StandardIOLanguageServerConnection( self._server_handle.stdin, self._server_handle.stdout, self.GetDefaultNotificationHandler())) self._connection.Start() try: self._connection.AwaitServerConnection() except language_server_completer.LanguageServerConnectionTimeout: LOGGER.error('clangd failed to start, or did not connect ' 'successfully') self.Shutdown() return LOGGER.info('clangd started') self.SendInitialize(request_data)
def ShouldEnableRustCompleter(): if not RLS_EXECUTABLE: LOGGER.error('Not using Rust completer: no RLS executable found at %s', RLS_EXECUTABLE) return False LOGGER.info('Using Rust completer') return True
def _SolutionTestCheckHeuristics(candidates, tokens, i): """ Test if one of the candidate files stands out """ path = os.path.join(*tokens[:i + 1]) selection = None # if there is just one file here, use that if len(candidates) == 1: selection = os.path.join(path, candidates[0]) LOGGER.info('Selected solution file %s as it is the first one found', selection) # there is more than one file, try some hints to decide # 1. is there a solution named just like the subdirectory with the source? if (not selection and i < len(tokens) - 1 and '{0}.sln'.format(tokens[i + 1]) in candidates): selection = os.path.join(path, '{0}.sln'.format(tokens[i + 1])) LOGGER.info('Selected solution file %s as it matches source subfolder', selection) # 2. is there a solution named just like the directory containing the # solution? if not selection and '{0}.sln'.format(tokens[i]) in candidates: selection = os.path.join(path, '{0}.sln'.format(tokens[i])) LOGGER.info( 'Selected solution file %s as it matches containing folder', selection) if not selection: LOGGER.error('Could not decide between multiple solution files:\n%s', candidates) return selection
def _ReaderLoop(self): """ Read responses from TSServer and use them to resolve the DeferredResponse instances. """ while True: self._tsserver_is_running.wait() try: message = self._ReadMessage() except (RuntimeError, ValueError): LOGGER.exception('Error while reading message from server') if not self._ServerIsRunning(): self._tsserver_is_running.clear() continue # We ignore events for now since we don't have a use for them. msgtype = message['type'] if msgtype == 'event': eventname = message['event'] LOGGER.info('Received %s event from TSServer', eventname) continue if msgtype != 'response': LOGGER.error('Unsupported message type', msgtype) continue seq = message['request_seq'] with self._pending_lock: if seq in self._pending: self._pending[seq].resolve(message) del self._pending[seq]
def StartServer( self, request_data ): with self._server_state_mutex: LOGGER.info( 'Starting %s: %s', self.GetServerName(), self.GetCommandLine() ) self._stderr_file = utils.CreateLogfile( '{}_stderr'.format( utils.MakeSafeFileNameString( self.GetServerName() ) ) ) with utils.OpenForStdHandle( self._stderr_file ) as stderr: self._server_handle = utils.SafePopen( self.GetCommandLine(), stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr = stderr ) self._connection = ( lsc.StandardIOLanguageServerConnection( self._server_handle.stdin, self._server_handle.stdout, self.GetDefaultNotificationHandler() ) ) self._connection.Start() try: self._connection.AwaitServerConnection() except lsc.LanguageServerConnectionTimeout: LOGGER.error( '%s failed to start, or did not connect successfully', self.GetServerName() ) self.Shutdown() return False LOGGER.info( '%s started', self.GetServerName() ) return True
def _SolutionTestCheckHeuristics( candidates, tokens, i ): """ Test if one of the candidate files stands out """ path = os.path.join( *tokens[ : i + 1 ] ) selection = None # if there is just one file here, use that if len( candidates ) == 1 : selection = os.path.join( path, candidates[ 0 ] ) LOGGER.info( 'Selected solution file %s as it is the first one found', selection ) # there is more than one file, try some hints to decide # 1. is there a solution named just like the subdirectory with the source? if ( not selection and i < len( tokens ) - 1 and u'{0}.sln'.format( tokens[ i + 1 ] ) in candidates ): selection = os.path.join( path, u'{0}.sln'.format( tokens[ i + 1 ] ) ) LOGGER.info( 'Selected solution file %s as it matches source subfolder', selection ) # 2. is there a solution named just like the directory containing the # solution? if not selection and u'{0}.sln'.format( tokens[ i ] ) in candidates : selection = os.path.join( path, u'{0}.sln'.format( tokens[ i ] ) ) LOGGER.info( 'Selected solution file %s as it matches containing folder', selection ) if not selection: LOGGER.error( 'Could not decide between multiple solution files:\n%s', candidates ) return selection
def GetClangdExecutableAndResourceDir(user_options): """Return the Clangd binary from the path specified in the 'clangd_binary_path' option. Let the binary find its resource directory in that case. If no binary is found or if it's out-of-date, return nothing. If 'clangd_binary_path' is empty, return the third-party Clangd and its resource directory if the user downloaded it and if it's up to date. Otherwise, return nothing.""" clangd = user_options['clangd_binary_path'] resource_dir = None if clangd: clangd = FindExecutable(ExpandVariablesInPath(clangd)) if not clangd: LOGGER.error('No Clangd executable found at %s', user_options['clangd_binary_path']) return None, None if not CheckClangdVersion(clangd): LOGGER.error('Clangd at %s is out-of-date', clangd) return None, None # Try looking for the pre-built binary. else: third_party_clangd = GetThirdPartyClangd() if not third_party_clangd: return None, None clangd = third_party_clangd resource_dir = CLANG_RESOURCE_DIR LOGGER.info('Using Clangd from %s', clangd) return clangd, resource_dir
def ShouldEnableTypeScriptCompleter(): tsserver = FindTSServer() if not tsserver: LOGGER.error( 'Not using TypeScript completer: TSServer not installed ' 'in %s', TSSERVER_DIR ) return False LOGGER.info( 'Using TypeScript completer with %s', tsserver ) return True
def _GetRlsVersion(): rls_version = _GetCommandOutput([RLS_EXECUTABLE, '--version']) match = RLS_VERSION_REGEX.match(rls_version) if not match: LOGGER.error('Cannot parse Rust Language Server version: %s', rls_version) return None return match.group('version')
def ShouldEnableTypeScriptCompleter( user_options ): tsserver = FindTSServer( user_options[ 'tsserver_binary_path' ] ) if not tsserver: LOGGER.error( 'Not using TypeScript completer: TSServer not installed ' 'in %s', TSSERVER_DIR ) return False LOGGER.info( 'Using TypeScript completer with %s', tsserver ) return True
def StartServer( self, request_data, project_directory = None ): with self._server_state_mutex: LOGGER.info( 'Starting jdt.ls Language Server...' ) if project_directory: self._java_project_dir = project_directory else: self._java_project_dir = _FindProjectDir( os.path.dirname( request_data[ 'filepath' ] ) ) self._workspace_path = _WorkspaceDirForProject( self._java_project_dir, self._use_clean_workspace ) command = [ PATH_TO_JAVA, '-Dfile.encoding=UTF-8', '-Declipse.application=org.eclipse.jdt.ls.core.id1', '-Dosgi.bundles.defaultStartLevel=4', '-Declipse.product=org.eclipse.jdt.ls.core.product', '-Dlog.level=ALL', '-jar', self._launcher_path, '-configuration', self._launcher_config, '-data', self._workspace_path, ] LOGGER.debug( 'Starting java-server with the following command: %s', command ) self._server_stderr = utils.CreateLogfile( 'jdt.ls_stderr_' ) with utils.OpenForStdHandle( self._server_stderr ) as stderr: self._server_handle = utils.SafePopen( command, stdin = PIPE, stdout = PIPE, stderr = stderr ) self._connection = ( language_server_completer.StandardIOLanguageServerConnection( self._server_handle.stdin, self._server_handle.stdout, self.GetDefaultNotificationHandler() ) ) self._connection.Start() try: self._connection.AwaitServerConnection() except language_server_completer.LanguageServerConnectionTimeout: LOGGER.error( 'jdt.ls failed to start, or did not connect ' 'successfully' ) self.Shutdown() return False LOGGER.info( 'jdt.ls Language Server started' ) return True
def StartServer(self, request_data, project_directory=None): with self._server_state_mutex: LOGGER.info('Starting jdt.ls Language Server...') if project_directory: self._java_project_dir = project_directory else: self._java_project_dir = _FindProjectDir( os.path.dirname(request_data['filepath'])) self._workspace_path = _WorkspaceDirForProject( self._java_project_dir, self._use_clean_workspace) command = [ PATH_TO_JAVA, '-Dfile.encoding=UTF-8', '-Declipse.application=org.eclipse.jdt.ls.core.id1', '-Dosgi.bundles.defaultStartLevel=4', '-Declipse.product=org.eclipse.jdt.ls.core.product', '-Dlog.level=ALL', '-jar', self._launcher_path, '-configuration', self._launcher_config, '-data', self._workspace_path, ] LOGGER.debug('Starting java-server with the following command: %s', command) self._server_stderr = utils.CreateLogfile('jdt.ls_stderr_') with utils.OpenForStdHandle(self._server_stderr) as stderr: self._server_handle = utils.SafePopen(command, stdin=PIPE, stdout=PIPE, stderr=stderr) self._connection = ( language_server_completer.StandardIOLanguageServerConnection( self._server_handle.stdin, self._server_handle.stdout, self.GetDefaultNotificationHandler())) self._connection.Start() try: self._connection.AwaitServerConnection() except language_server_completer.LanguageServerConnectionTimeout: LOGGER.error('jdt.ls failed to start, or did not connect ' 'successfully') self.Shutdown() return False LOGGER.info('jdt.ls Language Server started') return True
def StartServer(self, request_data, project_directory=None, wipe_workspace=False, wipe_config=False): try: with self._server_info_mutex: LOGGER.info('Starting jdt.ls Language Server...') if project_directory: self._java_project_dir = project_directory elif 'project_directory' in self._settings: self._java_project_dir = utils.AbsolutePath( self._settings['project_directory'], self._extra_conf_dir) else: self._java_project_dir = _FindProjectDir( os.path.dirname(request_data['filepath'])) self._workspace_path = _WorkspaceDirForProject( self._workspace_root_path, self._java_project_dir, self._use_clean_workspace) if not self._use_clean_workspace and wipe_workspace: if os.path.isdir(self._workspace_path): LOGGER.info( f'Wiping out workspace { self._workspace_path }') shutil.rmtree(self._workspace_path) self._launcher_config = _LauncherConfiguration( self._workspace_root_path, wipe_config) self._command = [ PATH_TO_JAVA ] + self._GetJvmArgs(request_data) + [ '-Dfile.encoding=UTF-8', '-Declipse.application=org.eclipse.jdt.ls.core.id1', '-Dosgi.bundles.defaultStartLevel=4', '-Declipse.product=org.eclipse.jdt.ls.core.product', '-Dlog.level=ALL', '-jar', self._launcher_path, '-configuration', self._launcher_config, '-data', self._workspace_path, ] return super(JavaCompleter, self)._StartServerNoLock(request_data) except language_server_completer.LanguageServerConnectionTimeout: LOGGER.error('%s failed to start, or did not connect successfully', self.GetServerName()) self.Shutdown() return False
def GetThirdPartyClangd(): pre_built_clangd = GetExecutable( PRE_BUILT_CLANDG_PATH ) if not pre_built_clangd: LOGGER.info( 'No Clangd executable found in %s', PRE_BUILT_CLANGD_DIR ) return None if not CheckClangdVersion( pre_built_clangd ): LOGGER.error( 'Clangd executable at %s is out-of-date', pre_built_clangd ) return None LOGGER.info( 'Clangd executable found at %s and up to date', PRE_BUILT_CLANGD_DIR ) return pre_built_clangd
def ShouldEnableRustCompleter(user_options): if GetExecutable(user_options['rls_binary_path']): if GetExecutable(user_options['rustc_binary_path']): return True else: LOGGER.error( 'rustc_binary_path not specified, rls_binary_path ignored') if not RLS_EXECUTABLE: LOGGER.error('Not using Rust completer: no RLS executable found at %s', RLS_EXECUTABLE) return False LOGGER.info('Using Rust completer') return True
def ShouldEnableRustCompleter(user_options): if (user_options['rls_binary_path'] and not user_options['rustc_binary_path']): LOGGER.error('Not using Rust completer: RUSTC not specified') return False rls = utils.FindExecutableWithFallback(user_options['rls_binary_path'], RLS_EXECUTABLE) if not rls: LOGGER.error('Not using Rust completer: no RLS executable found at %s', rls) return False LOGGER.info('Using Rust completer') return True
def Get3rdPartyClangd(): pre_built_clangd = os.path.abspath( os.path.join(os.path.dirname(__file__), '..', '..', '..', 'third_party', 'clangd', 'output', 'bin', 'clangd')) pre_built_clangd = utils.GetExecutable(pre_built_clangd) if not CheckClangdVersion(pre_built_clangd): error = 'clangd binary at {} is out-of-date please update.'.format( pre_built_clangd) global REPORTED_OUT_OF_DATE if not REPORTED_OUT_OF_DATE: REPORTED_OUT_OF_DATE = True raise RuntimeError(error) LOGGER.error(error) return None return pre_built_clangd
def _ComputeOffset(contents, line, column): """Compute the byte offset in the file given the line and column.""" contents = ToBytes(contents) current_line = 1 current_column = 1 newline = bytes(b'\n')[0] for i, byte in enumerate(contents): if current_line == line and current_column == column: return i current_column += 1 if byte == newline: current_line += 1 current_column = 1 message = COMPUTE_OFFSET_ERROR_MESSAGE.format(line=line, column=column) LOGGER.error(message) raise RuntimeError(message)
def ShouldEnableCsCompleter(user_options): user_roslyn_path = user_options['roslyn_binary_path'] if user_roslyn_path and not os.path.isfile(user_roslyn_path): LOGGER.error('No omnisharp-roslyn executable at %s', user_roslyn_path) # We should trust the user who specifically asked for a custom path. return False if os.path.isfile(user_roslyn_path): roslyn = user_roslyn_path else: roslyn = PATH_TO_OMNISHARP_ROSLYN_BINARY mono = FindExecutableWithFallback(user_options['mono_binary_path'], FindExecutable('mono')) if roslyn and (mono or utils.OnWindows()): return True LOGGER.info('No mono executable at %s', mono) return False
def ShouldEnableRustCompleter( user_options ): if ( 'rls_binary_path' in user_options and not user_options[ 'rust_toolchain_root' ] ): LOGGER.warning( 'rls_binary_path detected. ' 'Did you mean rust_toolchain_root?' ) if user_options[ 'rust_toolchain_root' ]: # Listen to what the user wanted to use ra = os.path.join( user_options[ 'rust_toolchain_root' ], 'bin', 'rust-analyzer' ) if not utils.FindExecutable( ra ): LOGGER.error( 'Not using Rust completer: no rust-analyzer ' 'executable found at %s', ra ) return False else: return True else: return bool( RA_EXECUTABLE )
def _OffsetToPosition(offset, filename, text, newlines): """Convert the 0-based codepoint offset |offset| to a position (line/col) in |text|. |filename| is the full path of the file containing |text| and |newlines| is a cache of the 0-based character offsets of all the \n characters in |text| (plus one extra). Returns responses.Position.""" for index, newline in enumerate(newlines): if newline >= offset: start_of_line = newlines[index - 1] + 1 if index > 0 else 0 column = offset - start_of_line line_value = text[start_of_line:newline] return responses.Location( index + 1, CodepointOffsetToByteOffset(line_value, column + 1), filename) # Invalid position - it's outside of the text. Just return the last # position in the text. This is an internal error. LOGGER.error("Invalid offset %s in file %s with text %s and newlines %s", offset, filename, text, newlines) raise RuntimeError("Invalid file offset in diff")
def StartServer( self, request_data ): with self._server_state_mutex: if self.ServerIsHealthy(): return # We have to get the settings before starting the server, as this call # might throw UnknownExtraConf. extra_conf_dir = self._GetSettingsFromExtraConf( request_data ) # Ensure we cleanup all states. self._Reset() LOGGER.info( 'Starting clangd: %s', self._clangd_command ) self._stderr_file = utils.CreateLogfile( 'clangd_stderr' ) with utils.OpenForStdHandle( self._stderr_file ) as stderr: self._server_handle = utils.SafePopen( self._clangd_command, stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr = stderr ) self._connection = ( language_server_completer.StandardIOLanguageServerConnection( self._server_handle.stdin, self._server_handle.stdout, self.GetDefaultNotificationHandler() ) ) self._connection.Start() try: self._connection.AwaitServerConnection() except language_server_completer.LanguageServerConnectionTimeout: LOGGER.error( 'clangd failed to start, or did not connect ' 'successfully' ) self.Shutdown() return LOGGER.info( 'clangd started' ) self.SendInitialize( request_data, extra_conf_dir = extra_conf_dir )
def __init__( self, user_options ): super( RustCompleter, self ).__init__( user_options ) self._racerd_binary = FindRacerdBinary( user_options ) self._racerd_port = None self._racerd_host = None self._server_state_lock = threading.RLock() self._keep_logfiles = user_options[ 'server_keep_logfiles' ] self._hmac_secret = '' self._rust_source_path = self._GetRustSrcPath() if not self._rust_source_path: LOGGER.warning( 'No path provided for the rustc source. Please set the ' 'rust_src_path option' ) elif not p.isdir( self._rust_source_path ): LOGGER.error( NON_EXISTING_RUST_SOURCES_PATH_MESSAGE ) raise RuntimeError( NON_EXISTING_RUST_SOURCES_PATH_MESSAGE ) if not self._racerd_binary: LOGGER.error( BINARY_NOT_FOUND_MESSAGE ) raise RuntimeError( BINARY_NOT_FOUND_MESSAGE ) self._StartServer()
def Get3rdPartyClangd(): pre_built_clangd = os.path.abspath( os.path.join( os.path.dirname( __file__ ), '..', '..', '..', 'third_party', 'clangd', 'output', 'bin', 'clangd' ) ) pre_built_clangd = utils.GetExecutable( pre_built_clangd ) if not CheckClangdVersion( pre_built_clangd ): error = 'clangd binary at {} is out-of-date please update.'.format( pre_built_clangd ) global REPORTED_OUT_OF_DATE if not REPORTED_OUT_OF_DATE: REPORTED_OUT_OF_DATE = True raise RuntimeError( error ) LOGGER.error( error ) return None return pre_built_clangd
def _ExecuteCommand(self, command, contents=None): """Run a command in a subprocess and communicate with it using the contents argument. Return the standard output. It is used to send a command to the Gocode daemon or execute the Godef binary.""" phandle = utils.SafePopen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdoutdata, stderrdata = phandle.communicate(contents) if phandle.returncode: message = SHELL_ERROR_MESSAGE.format(command=' '.join(command), code=phandle.returncode, error=ToUnicode( stderrdata.strip())) LOGGER.error(message) raise RuntimeError(message) return stdoutdata
def __init__(self, user_options): super(RustCompleter, self).__init__(user_options) self._racerd_binary = FindRacerdBinary(user_options) self._racerd_port = None self._racerd_host = None self._server_state_lock = threading.RLock() self._keep_logfiles = user_options['server_keep_logfiles'] self._hmac_secret = '' self._rust_source_path = self._GetRustSrcPath() if not self._rust_source_path: LOGGER.warning( 'No path provided for the rustc source. Please set the ' 'rust_src_path option') elif not p.isdir(self._rust_source_path): LOGGER.error(NON_EXISTING_RUST_SOURCES_PATH_MESSAGE) raise RuntimeError(NON_EXISTING_RUST_SOURCES_PATH_MESSAGE) if not self._racerd_binary: LOGGER.error(BINARY_NOT_FOUND_MESSAGE) raise RuntimeError(BINARY_NOT_FOUND_MESSAGE) self._StartServer()
def ComputeCandidatesInner(self, request_data): filename = request_data['filepath'] LOGGER.info('Gocode completion request %s', filename) contents = utils.ToBytes( request_data['file_data'][filename]['contents']) # NOTE: Offsets sent to gocode are byte offsets, so using start_column # with contents (a bytes instance) above is correct. offset = _ComputeOffset(contents, request_data['line_num'], request_data['start_column']) stdoutdata = self._ExecuteCommand([ self._gocode_binary_path, '-sock', 'tcp', '-addr', self._gocode_host, '-f=json', 'autocomplete', filename, str(offset) ], contents=contents) try: resultdata = json.loads(ToUnicode(stdoutdata)) except ValueError: LOGGER.error(GOCODE_PARSE_ERROR_MESSAGE) raise RuntimeError(GOCODE_PARSE_ERROR_MESSAGE) if not isinstance(resultdata, list) or len(resultdata) != 2: LOGGER.error(GOCODE_NO_COMPLETIONS_MESSAGE) raise RuntimeError(GOCODE_NO_COMPLETIONS_MESSAGE) for result in resultdata[1]: if result.get('class') == 'PANIC': raise RuntimeError(GOCODE_PANIC_MESSAGE) return [ responses.BuildCompletionData(insertion_text=x['name'], extra_data=x) for x in resultdata[1] ]
def _HasBinary(binary): binary_path = FindBinary(binary, user_options) if not binary_path: LOGGER.error('%s binary not found', binary_path) return binary_path