''' Update/generate 'tasks.json' file in .vscode subfolder. 'tasks.json' fields description: https://code.visualstudio.com/docs/editor/tasks ''' import os import json import utilities as utils import templateStrings as tmpStr import updatePaths as pth import updateWorkspaceSources as wks import updateMakefile as mkf import updateBuildData as build __version__ = utils.__version__ class Tasks(): def __init__(self): self.cPStr = wks.CPropertiesStrings() self.mkfStr = mkf.MakefileStrings() self.bStr = build.BuildDataStrings() def checkTasksFile(self): ''' Check if 'tasks.json' file exists. If it does, check if it is a valid JSON file. If it doesn't exist, create new according to template. ''' if utils.pathExists(utils.tasksPath): # file exists, check if it loads OK try: with open(utils.tasksPath, 'r') as tasksFile: json.load(tasksFile) print("Existing 'tasks.json' file found.") return except Exception as err: errorMsg = "Invalid 'tasks.json' file. Creating backup and new one.\n" errorMsg += "Possible cause: invalid json format or comments (not supported by this scripts). Error:\n" errorMsg += str(err) print(errorMsg) utils.copyAndRename(utils.tasksPath, utils.tasksBackupPath) self.createTasksFile() else: # 'tasks.json' file does not exist jet, create it according to template string self.createTasksFile() def createTasksFile(self): ''' Create fresh 'tasks.json' file. ''' try: with open(utils.tasksPath, 'w') as tasksFile: data = json.loads(tmpStr.tasksFileTemplate) dataToWrite = json.dumps(data, indent=4, sort_keys=False) tasksFile.seek(0) tasksFile.truncate() tasksFile.write(dataToWrite) print("New 'tasks.json' file created.") except Exception as err: errorMsg = "Exception error creating new 'tasks.json' file:\n" errorMsg += str(err) utils.printAndQuit(errorMsg) def getTasksData(self): ''' Get data from current 'tasks.json' file. File existance is previoulsy checked in 'checkTasksFile()'. ''' with open(utils.tasksPath, 'r') as tasksFile: data = json.load(tasksFile) return data def overwriteTasksFile(self, data): ''' Overwrite existing 'tasks.json' file with new data. ''' try: with open(utils.tasksPath, 'r+') as tasksFile: tasksFile.seek(0) tasksFile.truncate() dataToWrite = json.dumps(data, indent=4, sort_keys=False) tasksFile.write(dataToWrite) print("'tasks.json' file updated!") except Exception as err: errorMsg = "Exception error overwriting 'tasks.json' file:\n" errorMsg += str(err) utils.printAndQuit(errorMsg) def addOrReplaceTask(self, data, taskData): ''' Check wether tasks with this "label" already exists. If it doesn't, create new task, overwrite otherwise. ''' thisTaskName = taskData["label"] taskExist = False listOfTasks = data["tasks"] for taskIndex, task in enumerate(listOfTasks): if task["label"] == thisTaskName: # task with this name already exist, replace it's content data["tasks"][taskIndex] = taskData taskExist = True if not taskExist: data["tasks"].append(taskData) return data def addAllTasks(self, tasksData): ''' Merge and return all combined tasks data. ''' # building and compiling project tasks task = self.getBuildTask() tasksData = self.addOrReplaceTask(tasksData, task) task = self.getCompileTask() tasksData = self.addOrReplaceTask(tasksData, task) task = self.getDeleteBuildFolderTask() tasksData = self.addOrReplaceTask(tasksData, task) # debugging and target control tasts task = self.getBuildDownloadAndRunTask() tasksData = self.addOrReplaceTask(tasksData, task) task = self.getDownloadAndRunTask() tasksData = self.addOrReplaceTask(tasksData, task) task = self.getResetAndRunTask() tasksData = self.addOrReplaceTask(tasksData, task) task = self.getHaltTask() tasksData = self.addOrReplaceTask(tasksData, task) task = self.getRunTask() tasksData = self.addOrReplaceTask(tasksData, task) # update IDE workspace tasks task = self.getRunCurrentPythonFileTask() # common "run python file" task tasksData = self.addOrReplaceTask(tasksData, task) if utils.cubeMxProjectFilePath is not None: task = self.getOpenCubeMXTask() # open CubeMX project tasksData = self.addOrReplaceTask(tasksData, task) task = self.getUpdateTask() # update all files for VS Code so it can be used as IDE tasksData = self.addOrReplaceTask(tasksData, task) # TODO USER: User can add other custom tasks here # - copy any of getXTask() functions below, edit # - add this function here as other tasks above return tasksData ######################################################################################################################## # Build, compile and clean tasks ######################################################################################################################## def getBuildTask(self): ''' Add build task (execute 'make' command). Also the VS Code default 'build' task. ''' taskData = """ { "label": "will be replaced with templateStrings string", "group": { "kind": "build", "isDefault": true }, "type": "shell", "command": "specified below", "args": ["specified below"], "problemMatcher": { "pattern": { "regexp": "^(.*):(\\\\d+):(\\\\d+):\\\\s+(warning|error):\\\\s+(.*)$", "file": 1, "line": 2, "column": 3, "severity": 4, "message": 5 } }, "presentation": { "focus": true } } """ jsonTaskData = json.loads(taskData) buildData = build.BuildData().getBuildData() jsonTaskData["label"] = tmpStr.taskName_build jsonTaskData["command"] = buildData[self.bStr.buildToolsPath] gccFolderPath = os.path.dirname(buildData[self.bStr.gccExePath]) gccFolderPath = utils.pathWithForwardSlashes(gccFolderPath) jsonTaskData["args"] = ["GCC_PATH=" + gccFolderPath] # specify compiler path to make command numOfCores = os.cpu_count() parallelJobsNumber = int(numOfCores * 1.5) # https://stackoverflow.com/questions/15289250/make-j4-or-j8/15295032 parallelJobsStr = "-j" + str(parallelJobsNumber) jsonTaskData["args"].append(parallelJobsStr) # set 'make' parallel job execution return jsonTaskData def getCompileTask(self): ''' Create compile current file task (execute gcc compile command). ''' taskData = """ { "label": "will be replaced with templateStrings string", "type": "shell", "command": "will be replaced with GCC path below", "args": ["will be replaced with path from buildData.json"], "problemMatcher": { "pattern": { "regexp": "^(.*):(\\\\d+):(\\\\d+):\\\\s+(warning|error):\\\\s+(.*)$", "file": 1, "line": 2, "column": 3, "severity": 4, "message": 5 } }, "presentation": { "focus": true } } """ jsonTaskData = json.loads(taskData) # get compiler C flags, defines, includes, ... from 'buildData.json' buildData = build.BuildData().getBuildData() jsonTaskData["label"] = tmpStr.taskName_compile # defines cDefines = buildData[self.bStr.cDefines] cDefines = utils.preappendString(cDefines, '-D') # includes cIncludes = buildData[self.bStr.cIncludes] cIncludes = utils.preappendString(cIncludes, '-I') # build directory buildDir = buildData[self.bStr.buildDirPath] # c flags cFlags = buildData[self.bStr.cFlags] for flagIndex, flag in enumerate(cFlags): if flag == "-MF": newFlagString = "-MF'" + buildDir + "/${fileBasenameNoExtension}.d'" cFlags[flagIndex] = newFlagString continue # output file outputFilePath = "'" + buildDir + "/${fileBasenameNoExtension}.o'" outputFile = ["-o"] outputFile.append(outputFilePath) # compile file string fileString = "'${relativeFile}'" fileString = [fileString] jsonTaskData["command"] = buildData[self.bStr.gccExePath] jsonTaskData["args"] = ["-c"] # only compile switch jsonTaskData["args"].extend(cDefines) jsonTaskData["args"].extend(cIncludes) jsonTaskData["args"].extend(cFlags) jsonTaskData["args"].extend(fileString) jsonTaskData["args"].extend(outputFile) return jsonTaskData def getDeleteBuildFolderTask(self): ''' Create delete task (execute 'make clean' command). ''' taskData = """ { "label": "will be replaced with templateStrings string", "type": "shell", "command": "specified below", "args": ["clean"], "problemMatcher": [], "presentation": { "focus": false } } """ jsonTaskData = json.loads(taskData) buildData = build.BuildData().getBuildData() jsonTaskData["label"] = tmpStr.taskName_clean jsonTaskData["command"] = buildData[self.bStr.buildToolsPath] return jsonTaskData ######################################################################################################################## # Debugging and target control tasks ######################################################################################################################## def getBuildDownloadAndRunTask(self): ''' Create Build + Download and run task. Use 'dependsOn' feature to avoid doubling code. Note: If multiple 'dependOn' tasks are defined, these tasks are launched simultaneously, not chained one after another. ''' jsonTaskData = self.getDownloadAndRunTask() jsonTaskData["label"] = tmpStr.taskName_CPU_buildDownloadRun jsonTaskData["dependsOn"] = tmpStr.taskName_build return jsonTaskData def getDownloadAndRunTask(self): ''' Create Download and run task. ''' taskData = """ { "label": "will be replaced with templateStrings string", "type": "shell", "command": "specified below", "args": ["specified below"], "problemMatcher": [] } """ jsonTaskData = json.loads(taskData) buildData = build.BuildData().getBuildData() jsonTaskData["label"] = tmpStr.taskName_CPU_downloadRun jsonTaskData["command"] = buildData[self.bStr.openOcdPath] jsonTaskData["args"] = [] jsonTaskData["args"].append("-f") jsonTaskData["args"].append(buildData[self.bStr.openOcdInterfacePath]) for arg in buildData[self.bStr.openOcdConfig]: jsonTaskData["args"].append("-f") jsonTaskData["args"].append(arg) # -c program filename [verify] [reset] [exit] [offset] ([] are optional arguments) # Note: due problems with VS Code OpenOCD Tasks in case of workspace path containing spaces, target executable is passed # as relative path. workspacePath = utils.workspacePath jsonTaskData["args"].append("-c") programString = "program " + buildData[self.bStr.targetExecutablePath] + " verify reset exit" jsonTaskData["args"].append(programString) return jsonTaskData def getResetAndRunTask(self): ''' Create CPU: Reset and run task. ''' taskData = """ { "label": "will be replaced with templateStrings string", "type": "shell", "command": "specified below", "args": ["specified below"], "problemMatcher": [] } """ jsonTaskData = json.loads(taskData) buildData = build.BuildData().getBuildData() jsonTaskData["label"] = tmpStr.taskName_CPU_resetRun jsonTaskData["command"] = buildData[self.bStr.openOcdPath] jsonTaskData["args"] = [] jsonTaskData["args"].append("-f") jsonTaskData["args"].append(buildData[self.bStr.openOcdInterfacePath]) for arg in buildData[self.bStr.openOcdConfig]: jsonTaskData["args"].append("-f") jsonTaskData["args"].append(arg) jsonTaskData["args"].append("-c init") # init must be executed before other commands! jsonTaskData["args"].append("-c reset") jsonTaskData["args"].append("-c exit") return jsonTaskData def getHaltTask(self): ''' Create Halt/stop task. ''' taskData = """ { "label": "will be replaced with templateStrings string", "type": "shell", "command": "specified below", "args": ["specified below"], "problemMatcher": [] } """ jsonTaskData = json.loads(taskData) buildData = build.BuildData().getBuildData() jsonTaskData["label"] = tmpStr.taskName_CPU_halt jsonTaskData["command"] = buildData[self.bStr.openOcdPath] jsonTaskData["args"] = [] jsonTaskData["args"].append("-f") jsonTaskData["args"].append(buildData[self.bStr.openOcdInterfacePath]) for arg in buildData[self.bStr.openOcdConfig]: jsonTaskData["args"].append("-f") jsonTaskData["args"].append(arg) jsonTaskData["args"].append("-c init") # init must be executed before other commands! jsonTaskData["args"].append("-c halt") jsonTaskData["args"].append("-c exit") return jsonTaskData def getRunTask(self): ''' Create Run task. ''' taskData = """ { "label": "will be replaced with templateStrings string", "type": "shell", "command": "specified below", "args": ["specified below"], "problemMatcher": [] } """ jsonTaskData = json.loads(taskData) buildData = build.BuildData().getBuildData() jsonTaskData["label"] = tmpStr.taskName_CPU_run jsonTaskData["command"] = buildData[self.bStr.openOcdPath] jsonTaskData["args"] = [] jsonTaskData["args"].append("-f") jsonTaskData["args"].append(buildData[self.bStr.openOcdInterfacePath]) for arg in buildData[self.bStr.openOcdConfig]: jsonTaskData["args"].append("-f") jsonTaskData["args"].append(arg) jsonTaskData["args"].append("-c init") # init must be executed before other commands! jsonTaskData["args"].append("-c resume") jsonTaskData["args"].append("-c exit") return jsonTaskData ######################################################################################################################## # Other tasks ######################################################################################################################## def getRunCurrentPythonFileTask(self): ''' Create Run Python file task, which runs current active Python file. ''' taskData = """ { "label": "will be replaced with templateStrings string", "type": "shell", "command": "specified below", "args": [ "${file}" ], "presentation": { "focus": true }, "problemMatcher": [] } """ buildData = build.BuildData().getBuildData() jsonTaskData = json.loads(taskData) jsonTaskData["label"] = tmpStr.taskName_Python jsonTaskData["command"] = buildData[self.bStr.pythonExec] return jsonTaskData def getOpenCubeMXTask(self): ''' Create Open CubeMX project task. Starts with default program. Method of starting CubeMX differs across systems: - WIN: use standard 'start' cmd command to start default program for '.ioc' files - LINUX: does not associate itself with files by default. Use a program like "Main Menu" for GNOME to add CubeMX to the applications list, and then it can be selected as the default program for .ioc files. ''' taskData = """ { "label": "will be replaced with templateStrings string", "type": "shell", "command": "specified below", "args": ["specified below"], "presentation": { "focus": false }, "problemMatcher": [] } """ osIs = utils.detectOs() if osIs == "unix": openCubeCommand = "xdg-open" elif osIs == "osx": openCubeCommand = "/Applications/STMicroelectronics/STM32CubeMX.app/Contents/MacOs/STM32CubeMX" else: openCubeCommand = "start" jsonTaskData = json.loads(taskData) jsonTaskData["label"] = tmpStr.taskName_OpenCubeMX jsonTaskData["command"] = openCubeCommand jsonTaskData["args"] = [utils.cubeMxProjectFilePath] # opens with default program return jsonTaskData def getUpdateTask(self): ''' Create Update workspace task, which runs update.py script. ''' taskData = """ { "label": "will be replaced with templateStrings string", "type": "shell", "command": "specified below", "args": [ "${workspaceFolder}/ideScripts/update.py" ], "presentation": { "focus": true }, "problemMatcher": [] } """ buildData = build.BuildData().getBuildData() jsonTaskData = json.loads(taskData) jsonTaskData["label"] = tmpStr.taskName_updateWorkspace jsonTaskData["command"] = buildData[self.bStr.pythonExec] return jsonTaskData ######################################################################################################################## if __name__ == "__main__": utils.verifyFolderStructure() paths = pth.UpdatePaths() bData = build.BuildData() cP = wks.CProperties() makefile = mkf.Makefile() tasks = Tasks() # build data (update tools paths if neccessary) buildData = bData.prepareBuildData() # create taks file tasks.checkTasksFile() tasksData = tasks.getTasksData() tasksData = tasks.addAllTasks(tasksData) tasks.overwriteTasksFile(tasksData)