[FPM] Interactive debugging

Hello everyone,

I’m testing to use VSCode + FPM + gfortran (on windows with conda) to do interactive debugging.

Discussing with our new global friend ChatGPT it proposed me the following:
Add two files to my project within the .vscode folder:
task.json

{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "fpm-build-debug",
            "type": "shell",
            "command": "fpm build --profile debug"
        }
    ]
}

launch.json

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Debug Fortran with GDB",
            "type": "cppdbg",
            "request": "launch",
            "program": "${workspaceFolder}/build/debug/<executable-name>",
            "args": [],
            "stopAtEntry": false,
            "cwd": "${workspaceFolder}",
            "environment": [],
            "externalConsole": false,
            "MIMode": "gdb",
            "miDebuggerPath": "gdb",
            "preLaunchTask": "fpm-build-debug"
        }
    ]
}

This will not work because FPM does not produce predictable build directories.
So I have to hand-pick everytime I clean the project from whatever tag within
build\gfortran_XXXXXXXXXXX\app\<executable-name> or build\gfortran_XXXXXXXXXXX\test\<executable-name> etc etc.

Replacing manually in the launch.json the line "program": "${workspaceFolder}/build/debug/<executable-name>" enables interactive debugging when pressing F5, this is good but is not enough.

The ideal scenario, just like when working in python, would be to make it such that when having your current program file opened in the workspace, it could use this info to go and pick the right executable and pass it to GDB when pressing F5. This could in principle be double if it wasn’t for

FPM does not produce predictable build directories

Ideally, FPM should produce predictable build directories and it would make life easier. In the mean time, could there be some way of extracting the executables relative path when running fpm such that it could be passed to GDB ? I know that running fpm run --verbose or fpm test --verbose would print that path to the terminal, but I don’t know how to extract within this framework for GDB to catch it and use it. I’m exploring adding an intermediate python script as a runner for the debugging.

Any advise would be welcome.

Thanks,

1 Like

Hello!

I’m not sure whether this could work (I never used VS code), but, as far as I know, fpm provides the --runner option. Maybe you’ve already tried it, but I thought I should mention anyway. It should be possible to run a program through gdb by

fpm run my_program --profile debug --runner="gdb <gdb options>" -- <my_program arguments>

If you have a way to configure your launch.json file to run the command above, you are probably able to achieve what you want.

Note that my_program must match what VS code gives to FPM when you press F5: I guess it’s the filename without .f90 (or the likes).

I hope this may help.

As LLMs say, “happy debugging”.

EDIT

Now that I think about it, you could also “install” the executable in a temporary folder by

fpm install my_program --prefix="./debug" --profile debug

and use gdb as usual, by fetching the executable from the ./debug folder.

But I’m not sure whether the install sub-command:

  1. accepts a program name
  2. does not conflict with --profile debug

You may have to check this couple of things above, before employing this approach.

1 Like

You can have a look at what @matthiasnoback wrote on this topic. In the end, it’s similar to what @moriglia proposed with the `–runner` option. This approach can be automated by adding a task into VSCode as discussed in that thread. That discussion on GitHub may also be of help Integrating fpm with text editors (Sublime, Atom, VS Code) · fortran-lang/fpm · Discussion #479 · GitHub .

If you go for the install trick: build and install can be executed together using a .rsp file and creating your custom command.

1 Like

Yep, I was aware of all of those, but none is really solving the issue as they imply moving files, which is something I would prefer to avoid as it is not robust in the long run. But there are some ideas to review. Hopefully I’ll manage to get something working in a robust manner eventually. If I do, I’ll share it :wink:

1 Like

You can maybe create a task that will modify the launch.json with the output of the -–runner option.

Or do a PR on fpm to add the –-output argument and use it in place of the non-deterministic path :thinking:.

1 Like

… using the install option could enable mimicking the build hierarchy of CMake, and have a procedure that could be extended for both :thinking:

1 Like

Take a look at my settings for ForCAD (https://github.com/gha3mi/forcad/tree/main/.vscode). I’ve set it up on Linux to run tests and examples with different compilers, which might be useful.

2 Likes

Is “fpm build --list” which lists the pathnames or “fpm build --dump” which generates a TOML or JSON description of the model easy to incorporate for obtaining your pathnames?

seer interfaces gdb and fpm projects:nicely …

https://fortran-lang.discourse.group/t/seer-gui-frontend-to-gdb

I am not a VS user at the moment, but I though it had an fpm interface available that included debugger integration; can anyone confirm there is (or is not) something already available?

I have been meaning to change my own vim-based interface over to use the fpm API for ages now but never quite get to it but perhaps it mght serve as an example as-is of how to use the fpm command options to get the pathnames; although it uses Linux commands.

I suspect what you are asking for would be easy to do with a program calling the API.

1 Like

I’m a heavy user of VSCode because it is the most flexible editor availed for literally anything with any language. I’m not aware of any fpm-ready plug-in. There is Modern Fortran as a VSCode plugin for the linter but that’s about it, AFAIK.

When working with Python on VSCode I can press F5 on my current file and everything runs. I don’t need to do any special setups. When working with Microsoft Visual Studio + intel compilers I can also simply press F5 on my current project and it will work. This is what is missing :slight_smile:

2 Likes

@Ali one question, how do you set the compiler for debugging using this setup? (I’m reading it but haven’t yet tried it)

I saw you prepared several configurations but I’m just wondering what happens if you have already different compilers visible on your path, you press F5, which one is picked?

1 Like

You can choose the compiler from the list in the left menu at the top. After selecting it once, pressing F5 will run the program (the test or example that is open in the editor) using that configuration. You can also set breakpoints, see the screenshot. It works on Linux, on Windows it requires a few modifications.

2 Likes

Here is a simplified version of the settings above. I’ve only tested this on Linux with gdb, gdb-oneapi and cuda-gdb using gfortran, ifx and nvfortran, and it works for me.
It would be great if someone could test it on Windows and macOS as well. I’m not sure whether using cp via pwsh is fully reliable there.

.vscode/tasks.json
{
  "version": "2.0.0",
  "inputs": [
    {
      "id": "FC",
      "type": "pickString",
      "description": "Select Fortran compiler",
      "options": [
        "gfortran",
        "ifx",
        "nvfortran",
        "flang",
        "lfortran"
      ],
      "default": "gfortran"
    }
  ],
  "tasks": [
    {
      "label": "fpm",
      "type": "shell",
      "linux": {
        "command": "bash",
        "args": [
          "-c",
          "DEST='${workspaceFolder}/build/vscode'; mkdir -p \"$DEST\"; [[ '${relativeFileDirname}' == test* ]] && fpm test --target '${fileBasenameNoExtension}' --profile debug --compiler '${input:FC}' --runner cp -- \"$DEST\" || [[ '${relativeFileDirname}' == example* ]] && fpm run --example '${fileBasenameNoExtension}' --profile debug --compiler '${input:FC}' --runner cp -- \"$DEST\" || [[ '${relativeFileDirname}' == app* ]] && fpm run --profile debug --compiler '${input:FC}' --runner cp -- \"$DEST\" || exit 1"
        ]
      },
      "osx": {
        "command": "bash",
        "args": [
          "-c",
          "DEST='${workspaceFolder}/build/vscode'; mkdir -p \"$DEST\"; [[ '${relativeFileDirname}' == test* ]] && fpm test --target '${fileBasenameNoExtension}' --profile debug --compiler '${input:FC}' --runner cp -- \"$DEST\" || [[ '${relativeFileDirname}' == example* ]] && fpm run --example '${fileBasenameNoExtension}' --profile debug --compiler '${input:FC}' --runner cp -- \"$DEST\" || [[ '${relativeFileDirname}' == app* ]] && fpm run --profile debug --compiler '${input:FC}' --runner cp -- \"$DEST\" || exit 1"
        ]
      },
      "windows": {
        "command": "pwsh",
        "args": [
          "-NoLogo",
          "-NoProfile",
          "-Command",
          "$Dest='${workspaceFolder}/build/vscode'; New-Item -ItemType Directory -Force -Path $Dest | Out-Null; if ('${relativeFileDirname}' -like 'test*') { fpm test --target '${fileBasenameNoExtension}' --profile debug --compiler '${input:FC}' --runner cp -- $Dest } elseif ('${relativeFileDirname}' -like 'example*') { fpm run --example '${fileBasenameNoExtension}' --profile debug --compiler '${input:FC}' --runner cp -- $Dest } elseif ('${relativeFileDirname}' -like 'app*') { fpm run --profile debug --compiler '${input:FC}' --runner cp -- $Dest } else { exit 1 }"
        ]
      },
      "group": {
        "kind": "test",
        "isDefault": true
      },
      "presentation": {
        "echo": false,
        "reveal": "silent",
        "focus": true,
        "panel": "dedicated",
        "showReuseMessage": false,
        "clear": true
      }
    }
  ]
}
.vscode/launch.json
{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "fpm (gdb)",
      "type": "cppdbg",
      "request": "launch",
      "program": "${workspaceFolder}/build/vscode/${fileBasenameNoExtension}",
      "cwd": "${workspaceFolder}",
      "stopAtEntry": false,
      "externalConsole": false,
      "MIMode": "gdb",
      "linux": {
        "miDebuggerPath": "gdb"
      },
      "osx": {
        "miDebuggerPath": "gdb"
      },
      "windows": {
        "miDebuggerPath": "gdb.exe"
      },
      "preLaunchTask": "fpm"
    },
    {
      "name": "fpm (gdb-oneapi)",
      "type": "cppdbg",
      "request": "launch",
      "program": "${workspaceFolder}/build/vscode/${fileBasenameNoExtension}",
      "cwd": "${workspaceFolder}",
      "stopAtEntry": false,
      "externalConsole": false,
      "MIMode": "gdb",
      "linux": {
        "miDebuggerPath": "gdb-oneapi"
      },
      "osx": {
        "miDebuggerPath": "gdb-oneapi"
      },
      "windows": {
        "miDebuggerPath": "gdb-oneapi.exe"
      },
      "preLaunchTask": "fpm"
    },
    {
      "name": "fpm (cuda-gdb)",
      "type": "cppdbg",
      "request": "launch",
      "program": "${workspaceFolder}/build/vscode/${fileBasenameNoExtension}",
      "cwd": "${workspaceFolder}",
      "stopAtEntry": false,
      "externalConsole": false,
      "MIMode": "gdb",
      "linux": {
        "miDebuggerPath": "cuda-gdb"
      },
      "osx": {
        "miDebuggerPath": "cuda-gdb"
      },
      "windows": {
        "miDebuggerPath": "cuda-gdb.exe"
      },
      "preLaunchTask": "fpm"
    }
  ]
}

I’m trying it but having several errors, I think I have an idea I’ll let you know once I get it working.

I’m hitting several walls here… does somebody know if there is a way of building all targets (app, test, examples, etc) without running?

I created an empty test with fpm new. I’m using fpm 0.12.0, I don’t manage to get fpm install --test --bindir app --testdir test --prefix="./build/vscode" to build and copy anything from the test folder. it only moves the program from app.

@hkvzjal Just to confirm, are you encountering any errors with the files I posted above? and which operating system are you using? because it works with tests, examples, and app on my Linux system with different compilers.

Yes, the configuration for Powershell doesn’t work. I got several errors one after the other. I’m trying to merge the commands with an intermediate python script instead to manage all the filesystem related issues. The first issue I got is easy, the “command” should be “powershell” not “pwsh”, then there were others in the way the string is handled, another thing was about the "runner – cp " which did not work.

I’m testing on windows, once I get that working I’ll try on linux.

Powershell is a cool but tricky shell. For instance, when using the rsp file, you cannot invoke the command as @run because @is reserved. You can make the parser dumb by using the option --%. The equivalent of cp is copy-item in PS.

That’s why I’m trying to avoid powershell (and any platform specific shell for that matter) but use an intermediate python script to handle all the filesystem stuff in a clean, maintainable and cross-platform way.

1 Like