Subject: 28.0.50; Monorepos and project.el

From: Theodor Thornhill <[email-masked]>
Date: Fri, 19 Jun 2020 20:42:26 +0000

Hi!

At work I've had the following issue. Assume we have some sort of
gigarepo, like this maybe:

gigarepo/
├── clients
│   ├── client1
│   ├── client2
│   ├── client3
│   └── client4
└── services
    ├── service1
    ├── service2
    ├── service3
    └── service4

The services are made in different languages, say, some in c#, f#, java -
as are clients, some are Typescript projects, some are JS etc etc.

Everything is rooted in a ".git" in "gigarepo/", and there are no
submodules or any fancyness.

As project.el works now, there are several issues arising. I'll just
note them down here, and probably split things up later, if that is ok.

* Lsp server/client
  In most of the projects, the lsp servers are indexing from what they
  consider root. Typically a tsconfig.json, elm.json etc. They get
  confused, eglot a bit more than lsp-mode (it has its own root finding
  algorithm). What happens is they look in root (gigarepo/), and it has
  no executable for lsp-server. One solution is then to install the
  server in root, but then it indexes the whole thing, and gets super
  slow, and indexes a lot of unrelated stuff (I'm not even working on
  client 2-4.)
  
* Buffer switching
  Lets say several of the clients uses a module called AuthService.ts.
  If I'm working on several of these projects you get a lot of identical
  files, so it is a bit hit and miss.
  
* Grepping
  This one was the worst for me, since grepping was very slow given the
  size of the project, and grepping loads of unrelated files returns a
  lot of noise. 

What would be nice is to be able to get the benefits of the vc-dir
version of project.el, but not having to "git init" inside the child
projects. More specifically, to be able to choose "project context",
one as the closest project-root and one as the gigarepo project-root.

Also, If I've worked on both a service and a client, running git or
magit should probably be done from root rather than the subproject I am
currently in, to get the whole context.

Is there a way to do this? I realize this may be an odd situation, but
it came up nonetheless.

This report is getting long, and I think I'm already rambling a bit (and
forgetting stuff.), so I'll leave it for now.

Theo

P.S:
What I did to circumvent this (this is only a MVP, not at all optimized
for anything):

(defvar project-root-markers
  '("package.json"
    "tsconfig.json"
    "jsconfig.json"
    "elm.json"
    "*.sln")
  "Files or directories that indicate the root of a project.")

(defvar project-exclusion-list
  '("node_modules"
    "target"
    "build"
    "package-lock.json"
    "elm-stuff")
  "Things not to be included in project-find-file.")

(defun project-exclude-dirs ()
  (mapcar (lambda (dir)
          (add-to-list 'vc-directory-exclusion-list dir))
        project-exclusion-list))

(defun project-find-root (path)
  "Tail-recursive search in PATH for root markers."
  (let* ((this-dir (file-name-as-directory (file-truename path)))
         (parent-dir (expand-file-name (concat this-dir "../")))
         (system-root-dir (expand-file-name "/")))
    (cond
     ((project-root-p this-dir) (cons 'transient this-dir))
     ((equal system-root-dir this-dir) nil)
     (t (project-find-root parent-dir)))))

(defun project-root-p (path)
  "Check if current PATH has any of project root markers."
  (let ((results (mapcar (lambda (marker)
                           (file-exists-p (concat path marker)))
                         project-root-markers)))
    (eval `(or ,@ results))))