99
1010
1111MS_WINDOWS = (sys .platform == "win32" )
12+ APPLE = (sys .platform in ("darwin" , "ios" , "tvos" , "watchos" ))
13+
14+ COMMAND_TIMEOUT = 60.0
1215
1316
1417def normalize_text (text ):
@@ -19,6 +22,16 @@ def normalize_text(text):
1922 return text .strip ()
2023
2124
25+ def first_line (text ):
26+ # Get the first line. Return text unchanged if it's empty.
27+ lines = text .splitlines ()
28+ if lines :
29+ return lines [0 ]
30+ else :
31+ # text is an empty string
32+ return text
33+
34+
2235def read_first_line (filename ):
2336 # Get the first line of a text file and strip trailing spaces
2437 try :
@@ -293,9 +306,11 @@ def format_groups(groups):
293306 "BUILDPYTHON" ,
294307 "CC" ,
295308 "CFLAGS" ,
309+ "CI" ,
296310 "COLUMNS" ,
297311 "COMPUTERNAME" ,
298312 "COMSPEC" ,
313+ "CONTAINER" ,
299314 "CPP" ,
300315 "CPPFLAGS" ,
301316 "DISPLAY" ,
@@ -310,6 +325,7 @@ def format_groups(groups):
310325 "HOMEDRIVE" ,
311326 "HOMEPATH" ,
312327 "IDLESTARTUP" ,
328+ "IMAGE_OS_VERSION" ,
313329 "IPHONEOS_DEPLOYMENT_TARGET" ,
314330 "LANG" ,
315331 "LDFLAGS" ,
@@ -434,24 +450,47 @@ def format_attr(attr, value):
434450 info_add ('readline.library' , 'GNU readline' )
435451
436452
437- def collect_gdb ( info_add ):
453+ def run_command ( cmd , check = True , ** kwargs ):
438454 import subprocess
455+ timeout = COMMAND_TIMEOUT
439456
457+ cmd_str = ' ' .join (cmd )
440458 try :
441- proc = subprocess .Popen ([ "gdb" , "-nx" , "--version" ] ,
459+ proc = subprocess .Popen (cmd ,
442460 stdout = subprocess .PIPE ,
443- stderr = subprocess .PIPE ,
444- universal_newlines = True )
445- version = proc .communicate ()[0 ]
446- if proc .returncode :
447- # ignore gdb failure: test_gdb will log the error
448- return
449- except OSError :
450- return
461+ stderr = subprocess .DEVNULL ,
462+ text = True ,
463+ ** kwargs )
464+ with proc :
465+ try :
466+ stdout = proc .communicate (timeout = timeout )[0 ]
467+ except :
468+ proc .kill ()
469+ proc .communicate ()
470+ raise
451471
452- # Only keep the first line
453- version = version .splitlines ()[0 ]
454- info_add ('gdb_version' , version )
472+ if check and proc .returncode :
473+ print (f"Command { cmd_str } failed with exit code { proc .returncode } " )
474+ return ''
475+
476+ # Strip trailing spaces and newlines
477+ stdout = stdout .rstrip ()
478+ return stdout
479+ except FileNotFoundError :
480+ return ''
481+ except OSError as exc :
482+ print (f"Command { cmd_str } failed with: { exc !r} " )
483+ return ''
484+ except subprocess .TimeoutExpired :
485+ print (f"Command { cmd_str } : timeout!" )
486+ return ''
487+
488+
489+ def collect_gdb (info_add ):
490+ version = run_command (["gdb" , "-nx" , "--version" ])
491+ if version :
492+ # Only keep the first line
493+ info_add ('gdb_version' , first_line (version ))
455494
456495
457496def collect_tkinter (info_add ):
@@ -847,7 +886,6 @@ def collect_support_threading_helper(info_add):
847886
848887
849888def collect_cc (info_add ):
850- import subprocess
851889 import sysconfig
852890
853891 CC = sysconfig .get_config_var ('CC' )
@@ -860,23 +898,17 @@ def collect_cc(info_add):
860898 except ImportError :
861899 args = CC .split ()
862900 args .append ('--version' )
863- try :
864- proc = subprocess .Popen (args ,
865- stdout = subprocess .PIPE ,
866- stderr = subprocess .STDOUT ,
867- universal_newlines = True )
868- except OSError :
901+
902+ stdout = run_command (args )
903+ if not stdout :
869904 # Cannot run the compiler, for example when Python has been
870905 # cross-compiled and installed on the target platform where the
871906 # compiler is missing.
872- return
873-
874- stdout = proc .communicate ()[0 ]
875- if proc .returncode :
907+ #
876908 # CC --version failed: ignore error
877909 return
878910
879- text = stdout . splitlines ()[ 0 ]
911+ text = first_line ( stdout )
880912 text = normalize_text (text )
881913 info_add ('CC.version' , text )
882914
@@ -978,21 +1010,11 @@ def collect_windows(info_add):
9781010 call_func (info_add , 'windows.oem_code_page' , _winapi , 'GetOEMCP' )
9791011
9801012 # windows.version_caption: "wmic os get Caption,Version /value" command
981- import subprocess
982- try :
983- # When wmic.exe output is redirected to a pipe,
984- # it uses the OEM code page
985- proc = subprocess .Popen (["wmic" , "os" , "get" , "Caption,Version" , "/value" ],
986- stdout = subprocess .PIPE ,
987- stderr = subprocess .PIPE ,
988- encoding = "oem" ,
989- text = True )
990- output , stderr = proc .communicate ()
991- if proc .returncode :
992- output = ""
993- except OSError :
994- pass
995- else :
1013+ output = run_command (["wmic" , "os" , "get" , "Caption,Version" , "/value" ],
1014+ # When wmic.exe output is redirected to a pipe,
1015+ # it uses the OEM code page
1016+ encoding = "oem" )
1017+ if output :
9961018 for line in output .splitlines ():
9971019 line = line .strip ()
9981020 if line .startswith ('Caption=' ):
@@ -1005,23 +1027,11 @@ def collect_windows(info_add):
10051027 info_add ('windows.version' , line )
10061028
10071029 # windows.ver: "ver" command
1008- try :
1009- proc = subprocess .Popen (["ver" ], shell = True ,
1010- stdout = subprocess .PIPE ,
1011- stderr = subprocess .PIPE ,
1012- text = True )
1013- output = proc .communicate ()[0 ]
1014- if proc .returncode == 0xc0000142 :
1015- return
1016- if proc .returncode :
1017- output = ""
1018- except OSError :
1019- return
1020- else :
1021- output = output .strip ()
1022- line = output .splitlines ()[0 ]
1023- if line :
1024- info_add ('windows.ver' , line )
1030+ output = run_command (["ver" ], shell = True )
1031+ # "ver" output starts with an empty line: remove it
1032+ output = output .strip ()
1033+ if output :
1034+ info_add ('windows.ver' , first_line (output ))
10251035
10261036 # windows.developer_mode: get AllowDevelopmentWithoutDevLicense registry
10271037 value = winreg_query (r"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows"
@@ -1132,7 +1142,45 @@ def get_machine_id():
11321142 return None
11331143
11341144
1135- def collect_linux (info_add ):
1145+ def detect_virt ():
1146+ # Run systemd-detect-virt command
1147+ virt = run_command (["systemd-detect-virt" ], check = False )
1148+ if virt and virt != "none" :
1149+ return virt
1150+
1151+ # Check if the process in running in a container
1152+ import os .path
1153+ if os .path .exists ('/.dockerenv' ):
1154+ return 'docker'
1155+ if os .path .exists ('/run/.containerenv' ):
1156+ return 'podman'
1157+
1158+ container = read_first_line ('/run/systemd/container' )
1159+ if container :
1160+ return container
1161+
1162+ if APPLE :
1163+ hv_vmm_present = run_command (['sysctl' , '-n' , 'kern.hv_vmm_present' ])
1164+ if hv_vmm_present == '1' :
1165+ return 'run in a VM (kern.hv_vmm_present is 1)'
1166+
1167+ # Other ways to check if running in a container:
1168+ # * Parse /proc/1/mounts or /proc/1/mountinfo (check "/" filesystem).
1169+ # * Parse /proc/1/cgroup.
1170+ # * Parse the first line of /proc/1/sched (check process name is different
1171+ # than "init" and "systemd").
1172+ # * Check / inode.
1173+ # * On systems using SELinux (Fedora/CentOS/RHEL), check for "container_t"
1174+ # label, for example of /proc/1/attr/current.
1175+ # * Check for "container" variable in /proc/1/environ
1176+ # (only root can read this file).
1177+ # * Check for "container" environment variable.
1178+ # * Set a specific env var when creating the container image.
1179+ # * Run virt-what, need to install the script, and must be run as root.
1180+ # * Check for "GITHUB_ACTIONS" environmant variable (GitHub Action).
1181+
1182+
1183+ def collect_system (info_add ):
11361184 boot_id = read_first_line ("/proc/sys/kernel/random/boot_id" )
11371185 if boot_id :
11381186 info_add ('system.boot_id' , boot_id )
@@ -1152,6 +1200,15 @@ def collect_linux(info_add):
11521200 uptime = f'{ uptime } sec'
11531201 info_add ('system.uptime' , uptime )
11541202
1203+ virt = detect_virt ()
1204+ if virt :
1205+ info_add ('system.virt' , virt )
1206+
1207+ if APPLE :
1208+ hardware = run_command (['sysctl' , '-n' , 'hw.model' ])
1209+ if hardware :
1210+ info_add ('system.hardware' , hardware )
1211+
11551212
11561213def collect_info (info ):
11571214 error = False
@@ -1194,7 +1251,7 @@ def collect_info(info):
11941251 collect_zlib ,
11951252 collect_zstd ,
11961253 collect_libregrtest_utils ,
1197- collect_linux ,
1254+ collect_system ,
11981255
11991256 # Collecting from tests should be last as they have side effects.
12001257 collect_test_socket ,
0 commit comments