20 from Map.map
import mapAPI
21 from multiprocessing
import Pool
22 from multiprocessing
import Manager
23 from Extract.getCall
import getCallFunction
24 from Extract.pcresolveBridge
import buildCallsiteLookup
25 from Preprocess.preprocess
import codeProcess
26 from Repair.repair
import repairTask,validateByRun
27 from Tool.tool
import getAst,save2txt,loadConfig,removeParameter,buildRunCommand,resolveConfigFilePath,resolveConfigValuePath
28 from Tool.workspace
import cleanupRunWorkspace,createRunWorkspace,exportRunReport,getRepoRoot,getRuntimePaths,workspaceCwd
29 from Change.changeAnalyze
import isCompatible,addValueForAPI,updateSharedDict,querySharedDict,updateErrorLst
41 projName,libName,file,currentVersion,currentEnv,targetVersion,targetEnv,runCommand,runPath,lock,sharedDict,coverSet,runtimePaths=args
44 projName,libName,file,currentVersion,currentEnv,targetVersion,targetEnv,runCommand,runPath,lock,sharedDict,coverSet,runtimePaths,pcresolveLookup=args
45 copyRoot=runtimePaths[
'copy_root']
46 dataDir=runtimePaths[
'data_dir']
47 reportDir=runtimePaths[
'report_dir']
49 fileName = os.path.basename(file)[:-3]
53 normalized_file = file.replace(
'\\',
'/')
54 tempLst = normalized_file.split(
'/')
55 pos=tempLst.index(projName)
56 realProjPath=
'/'.join(tempLst[0:pos+1])
57 fileRelativePath=
'/'.join(tempLst[pos:])
58 copyFile = os.path.join(copyRoot, *fileRelativePath.split(
'/'))
60 callAPIDict,_=
getCallFunction(file,libName,realProjPath,pcresolveLookup=pcresolveLookup)
61 os.makedirs(dataDir, exist_ok=
True)
62 with open(os.path.join(dataDir, f
'{fileName}_callAPIDict.json'),
'w', encoding=
'utf-8')
as fw:
63 json.dump(callAPIDict, fw, indent=4, ensure_ascii=
False)
68 except Exception
as e:
70 invokedAPINum=len(callAPIDict)
71 errorLog = os.path.join(reportDir, f
'{projName}_fixed_log.txt')
72 for key,record
in callAPIDict.items():
75 callAPI=record[
'call_text']
76 lineNum=record[
'lineno']
77 formatAPI=record[
'format_api']
79 ansDict[key][
'Invoked API']=callAPI
80 ansDict[key][
'Location']=f
"At Line {lineNum} in {fileRelativePath}"
82 if callKey
not in coverSet:
83 ansDict[key][
'Coverage']=
'No'
86 ansDict[key][
'Coverage']=
'Yes'
93 currentMatch=matchDict[
'current']
94 targetMatch=matchDict[
'target']
96 currentMatch=
mapAPI(callAPI,runCommand,runPath,formatAPI,projName,libName,copyFile,currentVersion,currentEnv,lock,errLst,callKey=callKey,runtimePaths=runtimePaths)
97 targetMatch=
mapAPI(callAPI,runCommand,runPath,formatAPI,projName,libName,copyFile,targetVersion,targetEnv,lock,errLst,curr=0,callKey=callKey,runtimePaths=runtimePaths)
102 ansDict[key][f
"Definition @{currentVersion} <{currentMatch['matchMethod']}>"]=str(currentMatch[
'match'])
103 ansDict[key][f
"Definition @{targetVersion} <{targetMatch['matchMethod']}>"]=str(targetMatch[
'match'])
108 ansDict[key][
'Compatible']=
"Unknown"
110 errorMsg = f
"Error occurred, please check the {projName}_fixed_log.txt"
115 if len(repairLst)==0:
116 ansDict[key][
'Compatible']=
'Yes'
119 ansDict[key][
'Compatible']=
'Unknown'
120 ansDict[key][
'Repair <Unknown>']=
'AST parse failed'
121 errLst.append(f
"{callAPI}, AST parse failed in {fileRelativePath}: {astError}\n")
123 apiWithValue=
addValueForAPI(callAPI,projName,runPath,runCommand,currentEnv,targetEnv,errLst,callKey=callKey,runtimePaths=runtimePaths)
124 fixedAPI,compatibilityLabel,repairStatus=
repairTask(root,callAPI,apiWithValue,projName,runPath,runCommand,repairLst,targetEnv,errLst,callKey=callKey,runtimePaths=runtimePaths)
125 if compatibilityLabel==
'Compatible':
126 ansDict[key][
'Compatible']=
'Yes'
128 if compatibilityLabel==
'Incompatible':
129 ansDict[key][
'Compatible']=
'No'
131 ansDict[key][
'Compatible']=
'Unknown'
133 if repairStatus==
'Successful':
134 ansDict[key][
'Repair <Successful>']=f
"{fixedAPI}"
135 elif repairStatus==
'Failed':
136 ansDict[key][
'Repair <Failed>']=f
"{fixedAPI}"
138 ansDict[key][
'Repair <Unknown>']=f
"{fixedAPI}"
142 errorMsg = f
"Error occurred, please check the {projName}_fixed_log.txt"
151 return ansDict,fileRelativePath,invokedAPINum
168 def backward(projPath,libName,currentVersion,currentEnv,targetVersion,targetEnv,runCommand,runPath,workspace,pcresolveLookup=None):
170 copyRoot=runtimePaths[
'copy_root']
171 dataDir=runtimePaths[
'data_dir']
172 tempDir=runtimePaths[
'temp_dir']
173 reportDir=runtimePaths[
'report_dir']
175 pathObj.getPath(projPath)
176 filePath=[it
for it
in pathObj.path
if it.endswith(
'py')]
177 projName=os.path.basename(projPath)
178 errorLog = os.path.join(reportDir, f
'{projName}_fixed_log.txt')
179 if os.path.exists(errorLog):
184 if runPath
and runPath
not in runCommand:
185 cwd = os.path.join(copyRoot, projName, runPath)
187 cwd = os.path.join(copyRoot, projName)
188 print(
'Running the project...')
190 createResult = subprocess.run(
191 cmd, cwd=cwd, capture_output=
True, text=
True, encoding=
'utf-8'
193 if createResult.returncode!=0:
194 print(f
'Failure to generate PKL in current version')
195 print(createResult.stderr)
197 print(
"Running complete")
200 os.makedirs(tempDir,exist_ok=
True)
201 tempProjPath=os.path.join(tempDir,projName)
202 if os.path.exists(tempProjPath):
203 shutil.rmtree(tempProjPath)
204 shutil.move(os.path.join(copyRoot, projName), tempProjPath)
205 shutil.move(os.path.join(copyRoot, f
'bak_{projName}'), os.path.join(copyRoot, projName))
206 shutil.move(tempProjPath, os.path.join(copyRoot, f
'bak_{projName}'))
210 if pcresolveLookup
is None:
216 coverSet_path = os.path.join(copyRoot,
'pkl',
'coverSet')
217 if os.path.exists(coverSet_path):
218 with open(coverSet_path,
'r', encoding=
'utf-8')
as fr:
219 tempLst=fr.readlines()
221 it=it.rstrip(
'\n').replace(
' ',
'')
225 sharedDict=manager.dict()
226 tasks=[(projName,libName,file,currentVersion,currentEnv,targetVersion,targetEnv,runCommand,runPath,lock,sharedDict,coverSet,runtimePaths,pcresolveLookup)
for file
in filePath]
227 pool=Pool(processes=1)
228 resultLst=pool.map(backwardTask,tasks)
231 save2txt(resultLst, libName, runCommand, os.path.join(reportDir, f
'{projName}.txt'))
246 def run(config,cleanWorkspace=False):
251 projPath,runCommand,runPath,libName,currentVersion,targetVersion,currentEnv,targetEnv=
loadConfig(configPath)
267 print(f
"Run workspace: {workspace.workspace_root}")
268 print(
"Code preprocessing...")
274 codeProcess(projPath,runCommand,runPath,libName,workspace=workspace,pcresolveLookup=pcresolveLookup)
275 print(
"Code preprocess complete")
278 succeeded=
backward(projPath,libName,currentVersion,currentEnv,targetVersion,targetEnv,runCommand,runPath,workspace=workspace,pcresolveLookup=pcresolveLookup)
281 print(f
"Report output: {workspace.report_root}")
282 if cleanWorkspace
and succeeded:
284 print(f
"Run workspace removed: {workspace.run_root}")
291 parser=argparse.ArgumentParser(
292 description=
'Python API compatibility analysis and repair tool'
298 help=
'Configuration file path or file name under Configure',
303 help=
'Remove the run workspace after successful report export',
305 args=parser.parse_args()
309 run(args.config,cleanWorkspace=args.clean_workspace)
312 print(f
"Total run time={int(end-start)}s")
315 if __name__==
'__main__':
def isCompatible(current, target)
Determine the compatibility of APIs from current and target versions 判断起始版本和目标版本API的兼容性
def updateErrorLst(errorLog, errorLst)
Save error messages 保存错误信息
def updateSharedDict(callAPI, currentDict, targetDict, sharedDict)
Update API mapping dictionary: add and revise 更新API映射字典: 添加和修改
def querySharedDict(callAPI, sharedDict)
Query API mapping dictionary 查询API映射字典
def addValueForAPI(callAPI, projName, runPath, runCommand, currentEnv, targetEnv, errLst, callKey, *runtimePaths)
Add values stored by pkl file for API parameters 为API参数添加保存至pkl文件中的值
def getCallFunction(filePath, libName, projPath=None, pcresolveLookup=None)
Extract all API calls from a given .py file 每次传进来一个.py文件,抽取所有的调用API.
def backwardTask(args)
One process handles one file 一个进程处理一个文件
def run(config, cleanWorkspace=False)
Run PCART with an isolated workspace 在隔离工作区中运行PCART.
def backward(projPath, libName, currentVersion, currentEnv, targetVersion, targetEnv, runCommand, runPath, workspace, pcresolveLookup=None)
Generate pkl files and perform detection and repair tasks 生成项目调用API的pkl文件以及执行检测与修复任务
def main()
Main function of PCART PCART主函数
def mapAPI(callAPI, runCommand, runPath, formatAPI, projName, libName, copyFile, version, virtualEnv, lock, errLst, curr=1, *callKey, runtimePaths)
Construct the mapping between the invoked API and the lib API to obtain its signature 建立invoked API与 ...
def buildCallsiteLookup(projPath, libName)
Build a CallsiteRecord lookup table from PCResolve analysis 从PCResolve分析结果构建CallsiteRecord查找表
def codeProcess(projPath, runCommand, runPath, libName, workspace, pcresolveLookup=None)
Code processing 代码预处理
def repairTask(root, callAPI, apiWithValue, projName, runPath, runCommand, repairLst, virtualEnv, errLst, callKey, *runtimePaths)
Task of repairing parameter compatibility issues 参数兼容性问题修复任务
def getRepoRoot()
Get PCART repository root path 获取PCART仓库根目录路径
def cleanupRunWorkspace(workspace)
Remove one completed PCART run workspace 删除一次已完成的PCART运行工作区
def exportRunReport(workspace)
Export internal report files to user-visible report directory 将工作区内部报告导出到用户可见报告目录
def createRunWorkspace(repoRoot, projPath, runCommand, runPath, libName, currentVersion, targetVersion, currentEnv, targetEnv, commandId='cmd-001', timestamp=None)
Create an isolated workspace for one PCART run command 为一次PCART运行命令创建隔离工作区
def getRuntimePaths(workspace)
Return runtime artifact paths for the current execution 返回当前执行使用的运行产物路径