PCART  v1.4
Automated Repair of Python API Parameter Compatibility Issues
tool.py
Go to the documentation of this file.
1 
12 
13 
14 
15 import re
16 import os
17 import ast
18 import json
19 import hashlib
20 import platform
21 import shlex
22 from Path.getPath import Path
23 
24 
25 ARTIFACT_HASH_PATTERN=re.compile(r'(?:^|__)([0-9a-f]{64})$')
26 
27 
28 
33 def getArtifactHash(artifactId):
34  match=ARTIFACT_HASH_PATTERN.search(artifactId)
35  if match:
36  return match.group(1)
37  return ''
38 
39 
40 
45 def getArtifactDisplayName(artifactId):
46  artifactHash=getArtifactHash(artifactId)
47  if artifactHash and artifactId.endswith(artifactHash):
48  displayName=artifactId[:-len(artifactHash)].rstrip('_')
49  return displayName or artifactHash
50  return artifactId
51 
52 
53 
59 def shortenArtifactFileName(fileName,extension):
60  fileName=re.sub(r'[^0-9A-Za-z_.-]+','_',fileName).strip('._')
61  length=255-len(extension) if extension else 255
62  if len(fileName)<=length:
63  return fileName+extension
64  artifactHash=getArtifactHash(fileName)
65  if not artifactHash:
66  return fileName[:length]+extension
67  suffix='__'+artifactHash
68  prefixLength=max(0,length-len(suffix))
69  prefix=fileName[:prefixLength].rstrip('_')
70  return prefix+suffix+extension
71 
72 
73 
74 
85 def getParameter(p_string,separator=',',space=1):
86  #库定义的参数去空格,项目中的参数不去空格,防止出问题
87  if space: #默认是去空格的
88  p_string=p_string.replace(' ','') #去掉参数中的空格
89 
90  if p_string=='':
91  return []
92 
93  parameters=[]
94  stack=[]
95  count_left_min=0 #统计'('的个数
96  count_right_min=0 #统计')'的个数
97 
98  count_left_middle=0 #统计'['的个数
99  count_right_middle=0 #统计']'的个数
100 
101  count_left_hua=0 #统计'{'的个数
102  count_right_hua=0 #统计'}'的个数
103 
104  count_single_yinhao=0 #统计单引号的引号的个数
105  count_double_yinhao=0 #统计双引号的引号的个数
106 
107  for index,value in enumerate(p_string):
108  stack.append(value)
109  if (value=="'" or count_single_yinhao) and not count_double_yinhao: #若上一步出现了双引号,则说明此处的单引号是在双引号内的,所以不计算单引号的个数
110  if value=="'":
111  count_single_yinhao+=1
112  if count_single_yinhao&1:
113  continue
114 
115  elif (value=='"' or count_double_yinhao) and not count_single_yinhao: #若上一步出现了单引号,则说明此处的双引号是在单引号内的,所以不计算双引号的个数
116  if value=='"':
117  count_double_yinhao+=1
118  if count_double_yinhao&1:
119  continue
120 
121  count_single_yinhao=0 #重置为0
122  count_double_yinhao=0
123 
124  #只计算引号之外的括号是否成对出现
125  if value=='(':
126  count_left_min+=1
127  elif value==')':
128  count_right_min+=1
129 
130  elif value=='[':
131  count_left_middle+=1
132  elif value==']':
133  count_right_middle+=1
134 
135  elif value=='{':
136  count_left_hua+=1
137  elif value=='}':
138  count_right_hua+=1
139 
140 
141  #弹栈,遇到分隔符或达到字符串末尾
142  if value==separator:
143  flagMin=1 #假设左右括号的个数都是相等的
144  flagMid=1
145  flagHua=1
146  if '(' in stack:
147  if count_left_min!=count_right_min:
148  flagMin=0
149  if '[' in stack:
150  if count_left_middle!=count_right_middle:
151  flagMid=0
152  if '{' in stack:
153  if count_left_hua!=count_right_hua:
154  flagHua=0
155 
156  if flagMin and flagMid and flagHua:
157  parameters.append(''.join(stack[0:-1]))
158  stack.clear()
159 
160  elif index==len(p_string)-1:
161  parameters.append(''.join(stack))
162 
163 
164  return parameters
165 
166 
167 
173 def getAst(filePath,strFlag=0): #若strFlag=1,则表明传进来的是一个api,而不是一个路径
174  if strFlag==0:
175  with open(filePath,'r',encoding='UTF-8') as f:
176  s=f.read()
177  root=ast.parse(s,filename='<unknown>',mode='exec')
178  return root
179  root=ast.parse(filePath,filename='<unknown>',mode='exec')
180  return root
181 
182 
183 
184 
189 def getImportLst(filePath):
190  importLst=[]
191  with open(filePath,'r',encoding='UTF-8') as f:
192  lstR=f.readlines()
193  for it in lstR:
194  #这个判断条件在后续应该将其设置为通用的,不同的库判断条件不同
195  if (it[0:6]=='import' or it[0:4]=='from') and 'torch' in it and 'torchvision' not in it and 'torch_' not in it and '.torch' not in it:
196  importLst.append(it)
197  return importLst
198 
199 
200 
201 
210 def removeParameter(s,flag=0):
211  if '->' in s: #若有返回值,则把返回值也去掉
212  s=s.split('->')[0]
213  if flag==0: #去掉API中所有参数
214  stack=[]
215  left=0
216  right=0
217  ans=''
218  for index,value in enumerate(s):
219  #进栈
220  stack.append(value)
221  if value=='(':
222  left+=1
223  if value==')':
224  right+=1
225  #出栈
226  if left==right and left>0 and right>0:
227  pos=stack.index('(')
228  ans+=''.join(stack[0:pos])
229  stack.clear()
230  left=0
231  right=0
232  elif index==len(s)-1:
233  ans+=''.join(stack)
234  else: #只去除最后一个API的参数
235  i=len(s)-1
236  left=0 #记录左括号的个数
237  right=0
238  pos=len(s)
239  while i>=0:
240  if s[i]==')':
241  right+=1
242  if s[i]=='(':
243  left+=1
244  if left==right and left>0 and right>0:
245  pos=i #更新pos
246  break
247  i-=1
248  ans=s[0:pos]
249 
250  return ans
251 
252 
253 
254 
263  ans=''
264  left=0 #记录左括号的个数
265  right=0 #记录右括号的个数
266  pos=len(apiStr)
267  i=len(apiStr)-1
268  while i>=0:
269  if apiStr[i]==')':
270  right+=1
271  if apiStr[i]=='(':
272  left+=1
273  if left==right and left>0 and right>0:
274  pos=i
275  break
276  i-=1
277  if pos!=len(apiStr):
278  ans=apiStr[pos+1:-1]
279  return ans
280 
281 
282 
283 
292 def departAPI(s):
293  ansLst=[]
294  stack=''
295  leftMin=0 #记录左'('的个数
296  rightMin=0
297  leftMid=0
298  rightMid=0
299  for i in range(len(s)):
300  stack+=s[i]
301  if s[i]=='(':
302  leftMin+=1
303  if s[i]==')':
304  rightMin+=1
305  if s[i]=='[':
306  leftMid+=1
307  if s[i]==']':
308  rightMid+=1
309 
310  flagMid=1
311  if '[' in stack:
312  if leftMid!=rightMid:
313  flagMid=0
314 
315  #拆分函数字符串里面必须要出现()
316  if leftMin and rightMin and leftMin==rightMin and flagMid:
317  ansLst.append(stack[0:i+1])
318  leftMin=0
319  rightMin=0
320  if leftMid and rightMid:
321  leftMin=0
322  rightMid=0
323 
324  elif i==len(s)-1:
325  ansLst.append(stack[0:i+1])
326 
327  return ansLst
328 
329 
330 
331 
340 def departAPI2(s,separator='.'):
341  ansLst=[]
342  lst=[]
343  count_left_min=0 #统计左'('的个数
344  count_right_min=0 #统计右')'的个数
345 
346  count_left_middle=0 #统计左'['的个数
347  count_right_middle=0 #统计左']'的个数
348  for index,value in enumerate(s):
349  #入栈,分两种情况
350  if value!=separator:
351  lst.append(value)
352  if value=='(':
353  count_left_min+=1
354  if value==')':
355  count_right_min+=1
356  if value=='[':
357  count_left_middle+=1
358  if value==']':
359  count_right_middle+=1
360 
361  elif value==separator and ((count_left_min>count_right_min) or (count_left_middle>count_right_middle)):
362  lst.append(value)
363 
364  #弹栈,分三种情况
365  if value==separator:
366  flagMin=1 #假设左右括号的个数都是相等的
367  flagMid=1
368  if '(' in lst:
369  if count_left_min!=count_right_min:
370  flagMin=0
371  if '[' in lst:
372  if count_left_middle!=count_right_middle:
373  flagMid=0
374  if flagMin and flagMid:
375  ansLst.append(''.join(lst))
376  lst.clear()
377  elif index==len(s)-1:
378  ansLst.append(''.join(lst))
379 
380  return ansLst
381 
382 
383 
384 
389 def isDynamic(dic):
390  lst=list(dic.values())
391  for it in lst:
392  if isinstance(it,str):
393  return 1
394  return 0 #空字典也看作是模糊匹配的字典
395 
396 
397 
398 
403 def getVersionLst(libPath):
404  obj=Path('D')
405  versionLst=[]
406  obj.getPath(libPath)
407  path=obj.path
408  for p in path:
409  p = os.path.basename(p)
410  index=-1
411  for i in range(len(p)):
412  if p[i].isdigit():
413  index=i
414  break
415  versionLst.append(p[index:])
416  versionLst.sort(key=lambda it:cmp(it))
417  return versionLst
418 
419 
420 
421 
431 def cmp(version):
432  index=len(version)
433  innerVersion=""
434  for i in range(len(version)):
435  if version[i].isalpha():
436  index=i
437  break
438  if index!=len(version):
439  innerVersion=re.findall(r'\d+',version[index:])[0]
440  Type=re.findall(r'[a-zA-Z]+',version[index:])[0] #type='rc' or type='b' or type='a'
441  if Type=='a': #alpha版
442  if len(innerVersion)==1:
443  innerVersion='0.00000'+innerVersion
444  else:
445  innerVersion='0.0000'+innerVersion #为了确保a10<b1
446  elif Type=='b':#beta版
447  if len(innerVersion)==1:
448  innerVersion='0.000'+innerVersion
449  else:
450  innerVersion='0.00'+innerVersion #为了确保b10<rc1
451  elif Type=='rc': #release版
452  if len(innerVersion)==1:
453  innerVersion='0.0'+innerVersion
454  else:
455  innerVersion='0.'+innerVersion
456 
457 
458  lst=version[0:index].split('.')
459  if len(lst)==2:
460  lst.append('0')
461  if len(lst[1])==1:
462  lst[1]='0'+lst[1]
463  if len(lst[2])==1:
464  lst[2]='0'+lst[2]
465  v=''.join(lst)
466  if innerVersion=="":
467  return float(v)
468  else:
469  return float(v)-(1-float(innerVersion)) #转化为浮点数形式
470 
471 
472 
473 
479 def getFileName(fileName,extension):
480  if getArtifactHash(fileName):
481  return shortenArtifactFileName(fileName,extension)
482  #step1:先把fileName中的非法字符去除
483  fileName=fileName.replace(' ','')
484  fileName=fileName.replace('/','')
485  fileName=fileName.replace('\\','')
486  if len(extension) != 0:
487  length=255-len(extension)
488  #if len(fileName)>length:
489  #fileName=fileName[0:length] #如果超出了长度,就进行截断
490  fileName=fileName.split('(')[0] + '_' + hashlib.md5(fileName.encode()).hexdigest()[:16] # 2025.7.18 More robust file name processing
491  fileName=fileName[0:length]
492 
493  fileName+=extension
494  return fileName
495 
496 
497 
498 
507 def writeLine(width,s,fw):
508  if len(s)<=width-4:
509  tailSpaceNum=width-2-len(s)-1
510  fw.write('| '+s+' '*tailSpaceNum+'|'+'\n')
511  else:
512  s1=s[0:width-4]
513  s1='| '+s1+' |'+'\n'
514  fw.write(s1)
515  s2=s[width-4:]
516  if len(s2)<=width-4:
517  tailSpaceNum=width-2-len(s2)-1
518  s2='| '+s2+' '*tailSpaceNum+'|'+'\n'
519  fw.write(s2)
520  return
521  else:
522  writeLine(width,s2,fw) #递归拆分
523 
524 
525 
526 
533 def save2txt(lst,libName,runCommand,savePath):
534  fw=open(savePath,'w',encoding='UTF-8')
535  totalFileNum=0
536  totalAPINum=0
537  compatibleNum=0
538  incompatibleNum=0
539  unknownCompatibleNum=0
540  notCoverNum=0
541  successRepairNum=0
542  failedRepairNum=0
543  unknownRepairNum=0
544  for Tuple in lst: #计数
545  totalFileNum+=1
546  totalAPINum+=Tuple[2]
547  dic=Tuple[0]
548  for callAPI,subDict in dic.items():
549  if subDict['Coverage']=='No':
550  notCoverNum+=1
551  continue
552 
553  if subDict['Compatible']=='Yes':
554  compatibleNum+=1
555  elif subDict['Compatible']=='No':
556  incompatibleNum+=1
557  else:
558  unknownCompatibleNum+=1
559 
560  if 'Repair <Successful>' in subDict:
561  successRepairNum+=1
562  elif 'Repair <Failed>' in subDict:
563  failedRepairNum+=1
564  elif 'Repair <Unknown>' in subDict:
565  unknownRepairNum+=1
566 
567  #写结果
568  libName=libName.capitalize()
569  fw.write(f"Run Command: {runCommand}\n")
570  fw.write(f"Total File Number: {totalFileNum}\n")
571  fw.write(f"Total {libName} Invoked API Number: {totalAPINum}\n")
572  fw.write(f"Not Covered {libName} Invoked API Number: {notCoverNum}/{totalAPINum}\n")
573  fw.write(f"Covered {libName} Invoked API Number: {totalAPINum-notCoverNum}/{totalAPINum}\n\n")
574 
575  fw.write(f"Compatible {libName} Invoked API Number: {compatibleNum}/{totalAPINum-notCoverNum}\n")
576  fw.write(f"Unknown Compatible {libName} Invoked API Number: {unknownCompatibleNum}/{totalAPINum-notCoverNum}\n\n")
577 
578  fw.write(f"Incompatible {libName} Invoked API Number: {incompatibleNum}/{totalAPINum-notCoverNum}\n")
579  fw.write(f"-> Successfully Repaired {libName} Invoked API number: {successRepairNum}/{incompatibleNum}\n")
580  fw.write(f"-> Failed to Repair {libName} Invoked API Number: {failedRepairNum}/{incompatibleNum}\n")
581  fw.write(f"-> Unknown Repair Status {libName} Invoked API Number: {unknownRepairNum}/{incompatibleNum}\n\n")
582  fileCount=0
583  for Tuple in lst:
584  dic=Tuple[0]
585  filePath=Tuple[1]
586  invokedAPINum=Tuple[2]
587  fileCount+=1
588  width=102 #设置列表总宽度175个字符
589  title=f"File #{fileCount}: {filePath} has {invokedAPINum} {libName}-Invoked API(s)"
590  line='='*width+'\n'
591  fontSpaceNum=(width-len(title)-2)//2
592  tailSpaceNum=width-2-fontSpaceNum-len(title)
593  title='|'+' '*fontSpaceNum+title+' '*tailSpaceNum+'|'+'\n'
594  fw.write(line)
595  fw.write(title)
596  fw.write(line)
597  index=1
598  for callAPI,subDict in dic.items():
599  displayCall=subDict.pop('Invoked API',callAPI)
600  tempStr1=f"Invoked API #{index}: {displayCall}"
601  writeLine(width,tempStr1,fw)
602  fw.write('|'+' '*(width-2)+'|'+'\n')
603  cnt=1
604  for k,v in subDict.items():
605  tempStr2=f"{k}: {v}"
606  writeLine(width,tempStr2,fw)
607  if cnt!=len(subDict):
608  fw.write('|'+' '*(width-2)+'|'+'\n')
609  cnt+=1
610 
611  fw.write('|'+' '*(width-2)+'|'+'\n')
612  fw.write('|'+'-'*(width-2)+'|'+'\n')
613  if index!=len(dic):
614  fw.write('|'+' '*(width-2)+'|'+'\n')
615  index+=1
616  fw.write('\n\n')
617 
618  fw.close()
619 
620 
621 
622 
627 def loadConfig(configPath):
628  with open(configPath,'r',encoding='UTF-8') as fr:
629  dic=json.load(fr)
630  for key in dic:
631  if isinstance(dic[key], str):
632  dic[key] = dic[key].strip()
633  runCommand=dic['runCommand']
634  return dic['projPath'],runCommand,dic['runFilePath'],dic['libName'],dic['currentVersion'],dic['targetVersion'],dic['currentEnv'],dic['targetEnv']
635 
636 
637 
643 def resolveConfigFilePath(config,repoRoot):
644  if os.path.isabs(config):
645  return config
646  if os.path.exists(config):
647  return os.path.abspath(config)
648  return os.path.join(repoRoot,'Configure',config)
649 
650 
651 
657 def resolveConfigValuePath(repoRoot,path):
658  if not path:
659  return path
660  expanded=os.path.expanduser(path)
661  if os.name=='nt' and expanded.startswith('/') and not expanded.startswith('//'):
662  return expanded
663  if os.path.isabs(expanded):
664  return os.path.abspath(expanded)
665  return os.path.abspath(os.path.join(repoRoot,expanded))
666 
667 
668 
672 class UnsupportedRunCommand(Exception):
673  """Raised when PCART cannot safely execute a configured run command."""
674 
675 
676 
681 def isPythonExecutable(command):
682  baseName=os.path.basename(command.replace('\\','/')).lower()
683  if baseName in ('python','python.exe'):
684  return True
685  if re.match(r'^python3(\.\d+)?(\.exe)?$',baseName):
686  return True
687  return False
688 
689 
690 
695 def isPyLauncher(command):
696  baseName=os.path.basename(command.replace('\\','/')).lower()
697  return baseName in ('py','py.exe')
698 
699 
700 
706  if tokens and re.match(r'^-\d(\.\d+)?(-\d+)?$',tokens[0]):
707  return tokens[1:]
708  return tokens
709 
710 
711 
718 def normalizeRunCommand(runCommand):
719  tokens=shlex.split(runCommand)
720  if not tokens:
721  return runCommand,'python'
722 
723  # 兼容未加引号的Windows Python路径,避免shlex处理反斜杠
724  simpleTokens=runCommand.split(maxsplit=1)
725  if simpleTokens and isPythonExecutable(simpleTokens[0]):
726  rest=simpleTokens[1].lstrip() if len(simpleTokens)>1 else ''
727  return rest,'python'
728  if simpleTokens and isPyLauncher(simpleTokens[0]):
729  rest=simpleTokens[1].lstrip() if len(simpleTokens)>1 else ''
730  restTokens=shlex.split(rest)
731  return shlex.join(stripPyLauncherOption(restTokens)),'python'
732 
733  if isPythonExecutable(tokens[0]):
734  return shlex.join(tokens[1:]),'python'
735  if isPyLauncher(tokens[0]):
736  return shlex.join(stripPyLauncherOption(tokens[1:])),'python'
737  if tokens[0]=='-m' or tokens[0].endswith('.py'):
738  return runCommand,'python'
739  return runCommand,'console'
740 
741 
742 
748 def resolveConsoleExecutable(envPath,commandName):
749  normalizedEnvPath=os.path.abspath(envPath)
750  candidates=[]
751  if platform.system()=='Windows':
752  scriptsPath=os.path.join(normalizedEnvPath,'Scripts')
753  candidates.extend([
754  os.path.join(scriptsPath,commandName),
755  os.path.join(scriptsPath,commandName+'.exe'),
756  os.path.join(scriptsPath,commandName+'.cmd'),
757  os.path.join(scriptsPath,commandName+'.bat'),
758  ])
759  else:
760  candidates.append(os.path.join(normalizedEnvPath,'bin',commandName))
761 
762  for candidate in candidates:
763  if os.path.exists(candidate):
764  return candidate
765 
766  candidateStr=', '.join(candidates)
767  raise FileNotFoundError(
768  f"Cannot find console command '{commandName}' under virtual "
769  f"environment root: {normalizedEnvPath}. Tried: {candidateStr}"
770  )
771 
772 
773 
779 def buildRunCommand(runCommand,envPath):
780  normalizedCommand,commandType=normalizeRunCommand(runCommand)
781  if commandType=='python':
782  return [resolvePythonExecutable(envPath)] + shlex.split(normalizedCommand)
783 
784  args=shlex.split(normalizedCommand)
785  if not args:
786  raise UnsupportedRunCommand('Empty run command')
787  try:
788  commandPath=resolveConsoleExecutable(envPath,args[0])
789  except FileNotFoundError as e:
790  raise UnsupportedRunCommand(str(e)) from e
791  return [commandPath] + args[1:]
792 
793 
794 
799 def getRunFile(runCommand):
800  tokens=shlex.split(runCommand)
801  for token in tokens:
802  candidate=token.split('::',1)[0]
803  if candidate.endswith('.py'):
804  return candidate
805  return ''
806 
807 
808 
809 
814 def findPythonDir(basePath):
815  if not os.path.exists(basePath):
816  raise FileNotFoundError(
817  f"Cannot find Python directory: {basePath} does not exist"
818  )
819 
820  for entry in os.listdir(basePath):
821  full_path = os.path.join(basePath, entry)
822  if os.path.isdir(full_path):
823  if entry.startswith("python"):
824  return full_path
825 
826  raise FileNotFoundError(
827  f"Cannot find Python directory under {basePath}/pythonxx.xx"
828  )
829 
830 
831 
837 def resolveLibSourceCodePath(envPath,libName):
838  if platform.system() == 'Windows':
839  sitePackages=os.path.join(envPath, "Lib", "site-packages")
840  else:
841  sitePackages=os.path.join(findPythonDir(os.path.join(envPath, "lib")), "site-packages")
842 
843  if libName=="tensorflow":
844  tensorflowCore=os.path.join(sitePackages, "tensorflow_core")
845  if os.path.isdir(tensorflowCore):
846  return tensorflowCore
847 
848  return os.path.join(sitePackages, libName)
849 
850 
851 
857  normalizedEnvPath = os.path.abspath(envPath)
858  if platform.system() == 'Windows':
859  candidates = [
860  os.path.join(normalizedEnvPath, 'python.exe'),
861  os.path.join(normalizedEnvPath, 'Scripts', 'python.exe'),
862  ]
863  else:
864  candidates = [
865  os.path.join(normalizedEnvPath, 'bin', 'python'),
866  ]
867 
868  for candidate in candidates:
869  if os.path.exists(candidate):
870  return candidate
871 
872  candidate_str = ', '.join(candidates)
873  raise FileNotFoundError(
874  f"Cannot find Python executable under virtual environment root: "
875  f"{normalizedEnvPath}. Tried: {candidate_str}"
876  )
877 
878 
879 
886 def getSourceCodePath(configPath):
887  with open(f"Configure/{configPath}",'r',encoding='UTF-8') as fr:
888  dic=json.load(fr)
889  libName=dic['libName']
890  currentVersion=dic['currentVersion']
891  targetVersion=dic['targetVersion']
892  currentEnvPath=dic['currentEnv']
893  targetEnvPath=dic['targetEnv']
894 
895  currentSourceCodePath=resolveLibSourceCodePath(currentEnvPath, libName)
896  targetSourceCodePath=resolveLibSourceCodePath(targetEnvPath, libName)
897 
898  return currentVersion, targetVersion, currentSourceCodePath, targetSourceCodePath
899 
900 
901 
902 
904 class ConditionalReturnTransformer(ast.NodeTransformer):
905 
909  def visit_Return(self, node):
910  #检查return语句是否为单行条件语句(IfExp)
911  if isinstance(node, ast.Return) and isinstance(node.value, ast.IfExp):
912  ifExp = node.value
913  newIf = ast.If(
914  test=ifExp.test,
915  body=[ast.Return(value=ifExp.body)],
916  orelse=[ast.Return(value=ifExp.orelse)]
917  )
918  return newIf
919  return node
Convert conditional return statement to multi-line if-else structure 展开单行条件返回语句为多行if-else结构
Definition: tool.py:904
def visit_Return(self, node)
Transform single-line conditional return into multi-line if-else structure 将单行条件返回语句转换为多行if-else结构
Definition: tool.py:909
Unsupported run command exception 不支持的运行命令异常
Definition: tool.py:672
def buildRunCommand(runCommand, envPath)
Build subprocess argv for project run command 为被测项目运行命令构造subprocess argv.
Definition: tool.py:779
def removeParameter(s, flag=0)
Remove parameter(s) from API call string 去掉API中的参数部分
Definition: tool.py:210
def save2txt(lst, libName, runCommand, savePath)
Save PCART report 保存PCART报告
Definition: tool.py:533
def getAst(filePath, strFlag=0)
Get AST for code 将代码转化为Ast树
Definition: tool.py:173
def getArtifactDisplayName(artifactId)
Return readable artifact display name 返回可读运行产物展示名
Definition: tool.py:45
def getSourceCodePath(configPath)
Get source code path of the lib 获取库源码路径
Definition: tool.py:886
def getImportLst(filePath)
Extract import statements from a source file (currently torch-specific) 从源码文件中提取import语句(目前仅筛选torch相关...
Definition: tool.py:189
def resolveLibSourceCodePath(envPath, libName)
Resolve the source package directory for a library in a virtual environment 从虚拟环境中解析库源码目录
Definition: tool.py:837
def shortenArtifactFileName(fileName, extension)
Shorten artifact file name while preserving full hash 缩短运行产物文件名并保留完整hash.
Definition: tool.py:59
def cmp(version)
Normalize and compare version strings 归一化并比较版本号字符串
Definition: tool.py:431
def getVersionLst(libPath)
Get all version numbers from a library package directory 给定库的路径,获得该库的所有版本号
Definition: tool.py:403
def getFileName(fileName, extension)
Normalize file name 给文件取名字
Definition: tool.py:479
def resolveConfigValuePath(repoRoot, path)
Resolve path value loaded from PCART config 解析PCART配置字段中的路径值
Definition: tool.py:657
def getParameter(p_string, separator=',', space=1)
Split parameter string into list of separated parameters 将参数字符串拆分成单个的参数
Definition: tool.py:85
def writeLine(width, s, fw)
Format output 格式化输出
Definition: tool.py:507
def isDynamic(dic)
Check if a match dictionary contains dynamic (string) match results 检查匹配字典是否包含动态(字符串)匹配结果
Definition: tool.py:389
def resolveConfigFilePath(config, repoRoot)
Resolve PCART config file path 解析PCART配置文件路径
Definition: tool.py:643
def resolveConsoleExecutable(envPath, commandName)
Resolve console script executable from a virtual environment 从虚拟环境中解析console script可执行文件
Definition: tool.py:748
def isPyLauncher(command)
Check whether command is Windows py launcher 判断命令是否为Windows py启动器
Definition: tool.py:695
def departAPI2(s, separator='.')
Split API call string based on separator "." 根据"."拆分API调用字符串
Definition: tool.py:340
def loadConfig(configPath)
Load PCART's configuration file 加载PCART配置文件
Definition: tool.py:627
def departAPI(s)
Split API call string based on parameter passing 根据参数传递拆分API调用字符串
Definition: tool.py:292
def resolvePythonExecutable(envPath)
Resolve Python executable from a virtual environment root 从虚拟环境根目录解析 Python 解释器路径
Definition: tool.py:856
def normalizeRunCommand(runCommand)
Normalize project run command 归一化被测项目运行命令
Definition: tool.py:718
def getArtifactHash(artifactId)
Return artifact hash from an artifact id 从运行产物id中提取hash.
Definition: tool.py:33
def getRunFile(runCommand)
Extract the .py script filename from a run command 从运行命令中提取.py脚本文件名
Definition: tool.py:799
def isPythonExecutable(command)
Check whether command is a Python executable name/path 判断命令是否为Python解释器名称或路径
Definition: tool.py:681
def getLastAPIParameter(apiStr)
Get parameter(s) of the last API from the API call string 获取最后一个API参数
Definition: tool.py:262
def stripPyLauncherOption(tokens)
Remove version option after py launcher 去除py启动器后的版本选项
Definition: tool.py:705
def findPythonDir(basePath)
Get Python interpreter path 获取Python解释器路径
Definition: tool.py:814