diff --git a/src/python/jw/pkg/cmds/distro/lib/rpm.py b/src/python/jw/pkg/cmds/distro/lib/rpm.py index f7537353..509cc15f 100644 --- a/src/python/jw/pkg/cmds/distro/lib/rpm.py +++ b/src/python/jw/pkg/cmds/distro/lib/rpm.py @@ -24,7 +24,7 @@ async def all_installed_packages() -> Iterable[Package]: # export opts.append('--queryformat') tag_str = '|'.join([f'%{{{tag}}}' for tag in query_tags]) + r'\n' opts.append(tag_str) - package_list_str, stderr = await run_rpm(['-qa', *opts], sudo=False) + package_list_str, stderr, status = await run_rpm(['-qa', *opts], sudo=False) for package_str in package_list_str.splitlines(): tags = package_str.split('|') ret.append(Package(name=tags[0], vendor=tags[1], packager=tags[2], url=tags[3])) @@ -32,5 +32,5 @@ async def all_installed_packages() -> Iterable[Package]: # export async def list_files(pkg: str) -> list[str]: # export opts: list[str] = [] - file_list_str, stderr = await run_rpm(['-ql', pkg, *opts], sudo=False) + file_list_str, stderr, status = await run_rpm(['-ql', pkg, *opts], sudo=False) return file_list_str.splitlines() diff --git a/src/python/jw/pkg/cmds/projects/CmdBuild.py b/src/python/jw/pkg/cmds/projects/CmdBuild.py index e8e20e28..782211f5 100644 --- a/src/python/jw/pkg/cmds/projects/CmdBuild.py +++ b/src/python/jw/pkg/cmds/projects/CmdBuild.py @@ -115,7 +115,7 @@ class CmdBuild(Cmd): # export env = await get_profile_env(keep=keep) try: - stdout, stderr = await run_cmd( + stdout, stderr, status = await run_cmd( make_cmd, wd=wd, throw=True, diff --git a/src/python/jw/pkg/cmds/projects/CmdGetAuthInfo.py b/src/python/jw/pkg/cmds/projects/CmdGetAuthInfo.py index 489b4f95..1401e59e 100644 --- a/src/python/jw/pkg/cmds/projects/CmdGetAuthInfo.py +++ b/src/python/jw/pkg/cmds/projects/CmdGetAuthInfo.py @@ -35,7 +35,7 @@ class CmdGetAuthInfo(Cmd): # export if not os.path.isdir(jw_pkg_dir + '/.git'): log(DEBUG, f'jw-pkg directory is not a Git repo: {jw_pkg_dir}') return - remotes, stderr = await run_cmd(['git', '-C', jw_pkg_dir, 'remote', '-v']) + remotes, stderr, status = await run_cmd(['git', '-C', jw_pkg_dir, 'remote', '-v']) result: dict[str, str] = {} for line in remotes.splitlines(): name, url, typ = re.split(r'\s+', line) diff --git a/src/python/jw/pkg/cmds/projects/CmdListRepos.py b/src/python/jw/pkg/cmds/projects/CmdListRepos.py index 8755007c..fa8811f9 100644 --- a/src/python/jw/pkg/cmds/projects/CmdListRepos.py +++ b/src/python/jw/pkg/cmds/projects/CmdListRepos.py @@ -55,7 +55,7 @@ class CmdListRepos(Cmd): # export cmd_input = (f'-u {username}:{password}').encode('utf-8') curl_args.extend(['-K-']) curl_args.append(f'https://api.github.com/users/{args.from_owner}/repos') - repos = await run_curl(curl_args, cmd_input=cmd_input) + repos, stderr, status = await run_curl(curl_args, cmd_input=cmd_input) for repo in repos: print(repo['name']) return @@ -71,7 +71,7 @@ class CmdListRepos(Cmd): # export api_url = f'{args.base_url}/api/v1/{entities_dir}/{args.from_owner}/repos' try: tried.append(api_url) - repos = await run_curl(curl_args + [api_url], cmd_input=cmd_input) + repos, stderr, status = await run_curl(curl_args + [api_url], cmd_input=cmd_input) for repo in repos: print(repo['name']) break diff --git a/src/python/jw/pkg/lib/SSHClient.py b/src/python/jw/pkg/lib/SSHClient.py index 8790677a..873f563f 100644 --- a/src/python/jw/pkg/lib/SSHClient.py +++ b/src/python/jw/pkg/lib/SSHClient.py @@ -97,5 +97,5 @@ class SSHClientCmd(SSHClient): # export self.__init_askpass() cmd_arr = ['ssh'] cmd_arr.append(self.hostname) - stdout, stderr = await run_cmd(['ssh', self.hostname, cmd]) + stdout, stderr, status = await run_cmd(['ssh', self.hostname, cmd]) return stdout diff --git a/src/python/jw/pkg/lib/util.py b/src/python/jw/pkg/lib/util.py index 8ccef402..e327bfa8 100644 --- a/src/python/jw/pkg/lib/util.py +++ b/src/python/jw/pkg/lib/util.py @@ -76,13 +76,13 @@ async def run_cmd( def __make_pty_reader(collector: list[bytes], enc_for_verbose: str): def _read(fd): - data = os.read(fd, 1024) - if not data: - return data - collector.append(data) + ret = os.read(fd, 1024) + if not ret: + return ret + collector.append(ret) if verbose: - __log(NOTICE, data.decode(enc_for_verbose, errors="replace").rstrip("\n")) - return data + __log(NOTICE, ret.decode(enc_for_verbose, errors="replace").rstrip("\n")) + return ret return _read if verbose: @@ -128,16 +128,17 @@ async def run_cmd( os.environ.update(old_env) return pty.spawn(args, master_read=reader) - __check_exit_code(await asyncio.to_thread(_spawn)) + exit_code = await asyncio.to_thread(_spawn) + __check_exit_code(exit_code) # PTY merges stdout/stderr stdout_b = b"".join(stdout_chunks_b) if stdout_chunks_b else None if want_bytes: - return stdout_b, None + return stdout_b, None, exit_code stdout_dec_enc = (sys.stdout.encoding or "utf-8") if output_encoding is None else output_encoding stdout_s = stdout_b.decode(stdout_dec_enc, errors="replace") if stdout_b is not None else None - return stdout_s, None + return stdout_s, None, exit_code # -- non-interactive mode stdin = ( @@ -197,7 +198,7 @@ async def run_cmd( stderr_b = b"".join(stderr_parts_b) if stderr_parts_b else None if want_bytes: - return stdout_b, stderr_b + return stdout_b, stderr_b, exit_code if output_encoding is None: stdout_dec_enc = sys.stdout.encoding or "utf-8" @@ -212,7 +213,7 @@ async def run_cmd( if not want_bytes: __check_exit_code(exit_code, stdout=stdout_s, stderr=stderr_s) - return stdout_s, stderr_s + return stdout_s, stderr_s, exit_code finally: if cwd is not None: @@ -225,10 +226,10 @@ async def run_curl(args: list[str], parse_json: bool=True, wd=None, throw=None, if not verbose: cmd.append('-s') cmd.extend(args) - ret, stderr = await run_cmd(cmd, wd=wd, throw=throw, verbose=verbose, cmd_input=cmd_input) + ret, stderr, status = await run_cmd(cmd, wd=wd, throw=throw, verbose=verbose, cmd_input=cmd_input) if parse_json: try: - return json.loads(ret) + ret = json.loads(ret) except Exception as e: size = 'unknown number of' try: @@ -238,7 +239,7 @@ async def run_curl(args: list[str], parse_json: bool=True, wd=None, throw=None, print(f'Failed to parse {size} bytes output of command ' + f'>{pretty_cmd(cmd, wd)}< ({str(e)}): "{ret}"', file=sys.stderr) raise - return ret + return ret, stderr, status async def run_askpass(askpass_env: list[str], key: AskpassKey, host: str|None=None): assert host is None # Currently unsupported @@ -260,7 +261,7 @@ async def run_askpass(askpass_env: list[str], key: AskpassKey, host: str|None=No continue # Can't get user name from SSH_ASKPASS case AskpassKey.Password: exe_arg += 'Password' - ret, stderr = await run_cmd([exe, exe_arg], throw=False) + ret, stderr, status = await run_cmd([exe, exe_arg], throw=False) if ret is not None: return ret return None @@ -280,8 +281,7 @@ async def run_sudo(cmd: list[str], mod_env: dict[str, str] = {}, opts: list[str] cmdline.extend(cmd) if interactive: cmd_input = "mode:interactive" - stdout, stderr = await run_cmd(cmdline, throw=True, verbose=verbose, env=env, cmd_input=cmd_input) - return stdout, stderr + return await run_cmd(cmdline, throw=True, verbose=verbose, env=env, cmd_input=cmd_input) async def get_username(args: Namespace|None=None, url: str|None=None, askpass_env: list[str]=[]) -> str: # export url_user = None if url is None else urlparse(url).username @@ -329,7 +329,7 @@ async def get_profile_env(throw: bool=True, keep: Iterable[str]|bool=False) -> d } # Run bash as a login shell, which sources /etc/profile, then print environment as NUL-separated key=value pairs cmd = ['/usr/bin/env', '-i', '/bin/bash', '-lc', 'env -0'] - stdout, stderr = await run_cmd(cmd, throw=throw, output_encoding="bytes", verbose=True, env=env) + stdout, stderr, status = await run_cmd(cmd, throw=throw, output_encoding="bytes", verbose=True, env=env) ret: dict[str, str] = {} for entry in stdout.split(b"\0"): if not entry: