The infamous project root

I’m getting back to work on my Conventional Commit plugin after a period of inactivity and ran into an old, but still unresolved, problem: how should a plugin reliably find a configuration file that conceptually lives under the “project root”? Wait, don’t type “there is no project root” yet!

The plugin looks for a conventionalcommit.json file under the “project root” and, if present, uses it instead of the configuration bundled inside the plugin JAR. Back in 2019, when I first introduced that logic, it was already known that using Project.getBasePath or Project.getBaseDir was unreliable. Now this is further complicated by remote development (frontend/backend architecture) and the introduction of multi-project workspaces.

Consequently, questions that come to mind are:

  1. From the client perspective (where the user actually interacts with the plugin), is a “project root” always backed by LocalFileSystem? Or can a project be opened through other VirtualFileSystem implementations?
  2. The documentation of Project.getBasePath says “…if .idea directory is stored not near the project files”. But wait… is that a realistic scenario today? I haven’t found a way to create a fresh project where .idea is stored somewhere else than the “project root".
  3. Can we differentiate between “project root” and “opened folder path”? Many IDEs don’t have a real concept of a project, and the open folder is what extensions look at for something comparable to a “project root”.
  4. Given the points above, what is the recommended way for a plugin to locate a file that is expected to live under the “opened folder path”? Project.guessProjectDir is a known candidate, but I don’t like the “guessing” part, plus it seems it hardcodes assumptions around LocalFileSystem (see point 1).
  5. With multi-project workspaces, a user might have one conventionalcommit.json file per added project. In which case:
    1. How should a plugin decide which configuration file to use when a VCS operation involves files from different projects?
    2. Are there existing APIs/solutions for resolving configuration files in this scenario? (e.g., VCS root-based resolution - although VCS isn’t necessary enabled).

To be honest, this isn’t a question for which I’d expect a straightforward answer, but it’s a way to resurrect the topic and hopefully share some ideas around it.

It seems Project.getBaseDirectories() declared in BaseProjectDirectories should give you either 0 (no content entries), 1 or many (multi-project workspaces) directories results including those that are in WSL/Docker/etc.

Thanks. I’ll have to experiment with it to see if I can get a good result in all circumstances.

For posterity, I ended up creating my own function:

internal fun Project.findRootDir(): VirtualFile? {
  val baseDirs = getBaseDirectories()

  if (baseDirs.isNotEmpty()) {
    return baseDirs.first()
  }

  // The base path is not prefixed with a file:// schema
  val basePath = this.basePath

  if (basePath != null) {
    return LocalFileSystem.getInstance().findFileByPath(basePath)
  }

  return null
}

Why do you assume that project has a single root dir? It always may have multiple if more projects are linked.

Also even the only project may be opened from WSL2 location and not from LocalFileSystem, all paths will be WSL2

Why do you assume that project has a single root dir

I’ve tested multiple combination of projects (including a workspace with n projects) and that’s the option that suites best the plugin, as it has to look for a single .json file.

In the case of a workspace, the .json file will have to be created also under the workspace files, as that’s where the root dir will point to. I will obviously have to document this limitation.

may be opened from WSL2 location

Ah interesting. Indeed I did not try with WSL.
getBaseDirectories should already work as is, as it returns VirtualFile-s, but my basePath assumption will break. Doesn’t WSL also use the file:// protocol?