sasa1977 / boundary

Manage and restrain cross-module dependencies in Elixir projects
MIT License
818 stars 21 forks source link

Boundary recompiles every module if compilation fails #33

Closed QuinnWilton closed 3 years ago

QuinnWilton commented 3 years ago

I unfortunately don't have a minimal reproduction of this at the moment, but if you're interested, I can try to put one together later on today.

While converting a very large project to use Boundary, I noticed that my compilation times were very long, while I was fixing up compilation errors from moving around modules to conform to my boundaries. Eventually I realized that every module seemed to be getting recompiled after every change that resulted in an error.

Once all the errors were resolved, however, and I had one "successful" compilation behind me, only compile-time dependencies were getting re-compiled after a change, as expected.

Is this behavior something you'd expect to see? I'm not sure if it's a quirk of this specific mix compiler, or if there's something Boundary is doing that could be optimized further.

sasa1977 commented 3 years ago

This sounds strange. I didn't notice it myself, though I can't say that I've payed that much attention, because boundary is used on fairly small projects (about 100 modules (most of them are simple ecto schemas), about 10-15k LOC in total).

Boundary has a bit of hacky smartness to avoid adding compile-time dependencies. The expression use Boundary, deps: [Foo], exports: [Bar] introduces no compile-time dependency to Foo or Bar.

I think that it would be great if you could put a small repro example + repro procedure.

QuinnWilton commented 3 years ago

I think that it would be great if you could put a small repro example + repro procedure.

Sounds good! I've got a day full of meetings coming up, so I may not be able to put together the example immediately, but I'll try to get you one by the end of the day!

QuinnWilton commented 3 years ago

Edit: this doesn't actually work, see my followup comment.

Alright, it actually ended up being quicker than I expected to get a reproduction!

It's possible that I'm misunderstanding something, but the following repo should demonstrate what I'm talking about: https://github.com/QuinnWilton/slow_boundary_compile

Feel free to ask me questions if you're having trouble reproducing. I'm also available on Slack + Twitter DMs if it's easier to chat in realtime :)

QuinnWilton commented 3 years ago

Hold up -- I just realized I forgot to add the custom mix compiler in the above repo, so it's just showing the regular behavior of mix compile. I'll have to do some more experimentation to figure out exactly what's causing the slowdown I'm seeing.

In my large project, I was able to workaround the issue by temporarily removing the mix compiler from my project, until I resolved my errors, so there must be something more complicated going on than just what's in the above repo.

sasa1977 commented 3 years ago

I just realized I forgot to add the custom mix compiler in the above repo

That's already interesting, b/c it shows that Elixir itself recompiles a dependency if there's a compile-time warning, right? This might also be a part of the cause of the behaviour you're experiencing, because boundary emits warnings. But obviously we need more info. FYI, it's not likely I'll be able to look at this today, so definitely no hurry :-)

QuinnWilton commented 3 years ago

That's already interesting, b/c it shows that Elixir itself recompiles a dependency if there's a compile-time warning, right?

Yup, I was really surprised to see that behavior. It so closely aligned with the Boundary issue I'm seeing (or think I'm seeing?), that I thought it was a proper reproduction 😅 .

Sorry about the noise! I'll keep poking around and try to track down what specifically is happening in my project.

Unfortunately, there's thousands of modules, using most features of Elixir, so it could be anything causing the issue. I know a few of the libraries we use, like Absinthe, setup a lot of compile-time dependencies, so I'll see whether there's some interplay between Boundary and those.

sasa1977 commented 3 years ago

I noticed that my compilation times were very long, while I was fixing up compilation errors from moving around modules to conform to my boundaries

So I think I originally misunderstood this. If I read this correctly, you had std. compilation errors, i.e. those were not boundary warnings, right? If so, then it could be standard Elixir behaviour. As your repro repo demonstrates, compilation artifacts are not cached on compiler error. So one possibility is that boundary warnings forced you to make some changes which invalidated multiple modules, all which would have to be recompiled until the first fully successful compilation. Which is basically what you said in the description:

Once all the errors were resolved, however, and I had one "successful" compilation behind me, only compile-time dependencies were getting re-compiled after a change, as expected.

So if these errors are not boundary warnings, this looks to me like regular compiler behaviour. Does that make sense?

QuinnWilton commented 3 years ago

So I think I originally misunderstood this. If I read this correctly, you had std. compilation errors, i.e. those were not boundary warnings, right?

That's correct. In light of the knowledge that compilation artifacts are not cached until the compilation succeeds, then I think you're right, and this isn't anything specific to Boundary that's going on.

It's probably safe to close this then, and I'm happy to revisit it if I come across any evidence to suggest there's anything more going on than this. Sorry for the noise in opening this issue!

sasa1977 commented 3 years ago

OK, I'll close it for now, but feel free to revive it, or open up another one if something comes up.