Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Exclude folders bsp #3329

Merged
merged 30 commits into from
Aug 22, 2024
Merged

Exclude folders bsp #3329

merged 30 commits into from
Aug 22, 2024

Conversation

pawelsadlo
Copy link
Contributor

@pawelsadlo pawelsadlo commented Aug 1, 2024

fixes #3015

There are several limitations implied by intellijBSP client which led to this implementation:
mainly:

  1. There is no way to exclude a directory which is not inside a module content root aka baseDirectory (at least I didnt find a way - excluding .bsp is hardcoded in intellijBSP).
  2. Intellij won't include content root of module if it has no sources defined (this might be a bug in intellijBSP).

This implies that there should be at least one module which content root is equal to project root.

In some sitiations such module is defined by user in build.sc, but not always.
In other cases, a SyntheticRootBspBuildTargetData with bspTarget.baseDirectory == topLevelProjectRoot would be created to handle exclusions.

I went for a solution that is very local to bsp module.

Alternative solutions I though about:

  • make RootModule extend BSPModule
  • create synthetic RootModule with BspModule either during bootstrap if there is no module with BspTarget.baseDirectory = topLevelProjectDirectory
  • create synthetic RootModule with BspModule in bspWorker State

However personally I think that alternative solutions seem overkill for achieving such simple effect as excluding directories.

How it works:

  1. MillBuildServer.State contains additional field - Option[SyntheticRootBspBuildTargetData]
    SyntheticRootBspBuildTargetData contains minimal data required for BuildTarget to be reported bydef workspaceBuildTargets (forced by limitation 1).
  2. this field will be Some only if there is no normal module with BspBuildTarget.contentRoot == topLevelProjectRoot
    aka. module which can exclude topLevel folders
  3. if SyntheticRootBspBuildTargetData is created, it is reported in def workspaceBuildTargets together with other modules, but filtered out from other bspClient requests (exceptions: buildOutputPaths, and buildTargetSources)
  4. def buildTargetOutputPaths - for a module which has BspBuildTarget.contentRoot == topLevelProjectRoot (so potentially SyntheticRootBspBuildTargetData) i os.walk from topLevelProjectRoot looking for build.sc files and in their folders I exclude ".idea",".bsp","out",".bloop". - os.walk ensures exclusion of folders in nested mill projects
    note: following paths are excluded from walk
    def ignore(path: os.Path): Boolean = {
          path.last.startsWith(".") ||
          path.endsWith(os.RelPath("out")) ||
          path.endsWith(os.RelPath("target")) ||
          path.endsWith(os.RelPath("docs")) ||
          path.endsWith(os.RelPath("mill-build"))
        }

Actually if there was no limitations I mentioned, this would be the only required step.
5. Because of limitation 2 i had to report some source for SyntheticRootBspBuildTargetData - chosen "src", but this can be any, even non existing path.

Tested manually:

  • Navigation of sources
  • Navigation of meta-builds
  • Navigation of build.sc
  • excluded folders appear in IDE
  • solution does not collide with user-made RootModule in build.sc (actually it has to be a RootModule with JavaModule to be seen by BSP)

@lihaoyi
Copy link
Member

lihaoyi commented Aug 1, 2024

Thanks for the PR! Given the topic at hand, I wonder if @jastice has any opinions here on what's the right way to do things. He's the author of BSP on the intellij side, and would likely know more than me the limitations, potential workarounds, and expected future development of the IntelliJ BSP client

@jastice
Copy link

jastice commented Aug 1, 2024

Forwarding to @AnthonyGrod who is currently working on integrating Mill with the new IJ BSP plugin

@pawelsadlo pawelsadlo marked this pull request as ready for review August 1, 2024 15:38
@AnthonyGrod
Copy link
Contributor

AnthonyGrod commented Aug 2, 2024

Hello, at the moment I'm focusing on very basic functionalities of Mill over IntelliJ-BSP plugin integration, but I'll be happy to help when I will finish the basic support.

@lihaoyi
Copy link
Member

lihaoyi commented Aug 2, 2024

@pawelsadlo can you explain in the PR description how your solution works?

@pawelsadlo
Copy link
Contributor Author

@lihaoyi wrote a description, if anything needs elaboration, tag me

@lihaoyi
Copy link
Member

lihaoyi commented Aug 2, 2024

Thanks! Will take a look. @lefou should probably review this too since he's more familiar with the BSP side of things

@lihaoyi lihaoyi requested review from lihaoyi and lefou August 2, 2024 16:26
@lihaoyi
Copy link
Member

lihaoyi commented Aug 3, 2024

At a quick glance this looks reasonable to me

path.last.startsWith(".") ||
path.endsWith(os.RelPath("out")) ||
path.endsWith(os.RelPath("target")) ||
path.endsWith(os.RelPath("docs")) ||
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we ignoring docs/? I'm not aware of such a folder name being treated specially by any build tool

Copy link
Contributor Author

@pawelsadlo pawelsadlo Aug 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those are patches ignored during the os.walk in the search of build.sc files (so basically a subprojects), I assumed that we don't expect a subproject to bo located inside docs directory.

The logic is:

  1. We recursively search for build.sc files under top level root of our project. (Directories containing build.sc are potential directories where out, .idea, .bsp , .bloop would be generated).
    I search for build.sc instead of .idea, out, .bsp and .bloop because the latter may not be present at the time of bsp import, and may appear later (for example during build of subproject)
  2. We exclude .bsp,.idea,.bloop,out from folders of found in step 1.

Copy link
Member

@lefou lefou left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still think we try to fight a symptom of a bad behaving BSP client here. BSP is about declaring what makes a project, not what-not makes a project. If the BSP client (the IDE) handles more than that, we simply can't and shouldn't control that via BSP.

Hence, I don't think I can properly review this PR, as I don't agree on it's purpose.

Comment on lines 116 to 123
def additionalExclusions = projectsRootPaths.flatMap { path =>
Seq(
outputPathItem(path / ".idea"),
outputPathItem(path / "out"),
outputPathItem(path / ".bsp"),
outputPathItem(path / ".bloop")
)
}
Copy link
Member

@lefou lefou Aug 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not convinced excluding hardcoded directories is the right approach for a generic protocol. Esp. since some of these directories aren't even created or managed by us.

@lihaoyi
Copy link
Member

lihaoyi commented Aug 6, 2024

@pawelsadlo can you point out where in the code we set the excluded folders to be marked as excluded in BSP, and therefore in IntelliJ? I've been trying to ready through the code and haven't figured out how it works, although testing it out it does the right thing

@lefou I think we should do a best-effort here. It's not super elegant, and maybe it should be the IDE's responsibility to read the gitignore file and ignore folders. IntelliJ actually does have that functionality, but it's not automatic (you have to navigate to gitignore and click click), and it doesn't seem fully functional (e.g. it doesn't help you exclude .idea/). I agree it's not great from an implementation architecture perspective, and it's not really generalizable, but I think it's worth doing regardless. If IntelliJ starts handling this automatically on their side at some point in the future, we can re-consider and remove our special casing as necessary

Comment on lines 101 to 105
def ignore(path: os.Path): Boolean = {
path.last.startsWith(".") ||
path.last == "out" ||
path.last == "target"
}
Copy link
Member

@lihaoyi lihaoyi Aug 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pawelsadlo rather than hardcoding things here, and below in additionalExclusions, let's use the .gitignore. We can use JGit to perform these git check-ignore checks quickly without the subprocess overhead using the classes from org.eclipse.jgit.ignore.*. We'd also need to check the equivalent of git ls-files (e.g. TreeWalk) to see if the folder has been added to the index, since folders explicitly added to the git index bypass the gitignore. That will generalize this to support everything that is gitignored, which is probably what a developer working with an arbitrary git codebase wants.

Special-casing git should be the right thing for 99% of developers who use Git for version control today. If .gitignore is not present, we can fall back to only ignoring the top-level out/, .idea/, and .bsp/ folders. I don't think we should traverse recursively and try to find stuff in subfolders.

It's possible someone will come in future asking for hg integration or something, but we can cross that bridge when we get to it

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Of course ,however I am on vacation now, I will do this as soon as I have access to my laptop.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure! no hurry

@lihaoyi
Copy link
Member

lihaoyi commented Aug 6, 2024

@pawelsadlo can you also manually test to verify that generated source files inside the excluded out/ folder gets correctly picked up as sources, and doesn't get excluded accidentally?

sanitizeUri(path) + "/",
OutputPathItemKind.DIRECTORY
)
def additionalExclusions = projectsRootPaths.flatMap { path =>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lihaoyi these folders are excluded ones, they then are sent to bsp by buildoutputpaths

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it. Is everything in buildTargetOutputPaths considered excluded?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Formally, according to BSP docs it's BSP client decision what to do with buildOutputPaths response from BSP server. most of clients are excluding them, in particular intellij.
What's more ,in case of intellij there are the limitations I mentioned in the PR description (these are not formalized though ,it's just what I have observed by reading through intellij bsp source code and confirmed by testing )

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it. Can you also manually test VSCode+Metals to see what the behavior is there? Just so we know what affect this PR will have on those users (if any)

@pawelsadlo
Copy link
Contributor Author

@pawelsadlo can you also manually test to verify that generated source files inside the excluded out/ folder gets correctly picked up as sources, and doesn't get excluded accidentally?

Sure

@lihaoyi
Copy link
Member

lihaoyi commented Aug 19, 2024

@pawelsadlo just bump on this :) if you don't have the time to continue, I can pay out half the bounty and finish off the PR myself

@pawelsadlo
Copy link
Contributor Author

@pawelsadlo just bump on this :) if you don't have the time to continue, I can pay out half the bounty and finish off the PR myself

@lihaoyi I'm ok with half of bounty , I'll be busy next week anyway, so can't do it soon.

@lihaoyi
Copy link
Member

lihaoyi commented Aug 22, 2024

I took over the PR and removed the os.walk logic and made it ignore only top-level out/.bsp/.idea/.bloop for now. Further improvements to integrate with .gitignore etc. can come, but this should get us 90% of the value without too much complexity

Manually tested with normal Scala and Java projects with root module, without root module, and with meta-build. All seem to work, with navigation intact in all source and build files and the out/ and .idea/ folders correctly ignored

@lihaoyi lihaoyi merged commit 5041301 into com-lihaoyi:main Aug 22, 2024
31 checks passed
@lihaoyi
Copy link
Member

lihaoyi commented Aug 22, 2024

@pawelsadlo can send me an email with your bank transfer details and i can close out the bounty

@lihaoyi lihaoyi added this to the 0.12.0 milestone Aug 22, 2024
@pawelsadlo
Copy link
Contributor Author

pawelsadlo commented Aug 24, 2024

@lihaoyi sent you the details

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

IntelliJ BSP does not mark out/ and .idea/ folders as excluded (500USD Bounty)
5 participants