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

Add --link flag to compile to allow linking through BSP #2535

Merged
merged 26 commits into from
Dec 31, 2024

Conversation

KristianAN
Copy link
Contributor

Before this commit linking could only be done through the run command with BSP. Because the BSP protocol does describe a linking command (as it is often a part of the compile step) the flag --link has been added to the compile command. This is useful for those kinds of projects that need the linker to be run to produce source code for further toolchains, e.g. a ScalaJS project might often use @JSExport and then run the code from some other JS entrypoint.

@KristianAN
Copy link
Contributor Author

@tgodzik I'm not quite sure where/how I can write a test for this. I looked at BspCompileSpec, but I'm not quite sure how I can pass in the compileArgs there.

I also want to further investigate some bugs with the JS linker config as it does not seem to respect everything that is set from bloop-config, but maybe that should be it's own PR?

Before this commit linking could only be done through the run command with BSP.
Because the BSP protocol does describe a linking command (as it is often a part
of the compile step) the flag --link has been added to the compile command. This
is useful for those kinds of projects that need the linker to be run to produce
source code for further toolchains, e.g. a ScalaJS project might often use
@JSExport and then run the code from some other JS entrypoint.
@KristianAN
Copy link
Contributor Author

this should also fix: #1304

@KristianAN
Copy link
Contributor Author

The JS-bridge defines this:

    val moduleInitializers = mainClass match {
      case Some(mainClass) if runMain =>
        logger.debug(s"Setting up main module initializers for $project")
        List(ModuleInitializer.mainMethodWithArgs(mainClass, "main"))
      case _ =>
        if (runMain) {
          logger.debug(s"Setting up no module initializers, commonjs module detected $project")
          Nil // If run is disabled, it's a commonjs module and we link with exports
        } else {
          // There is no main class, install the test module initializers
          logger.debug(s"Setting up test module initializers for $project")
          List(
            ModuleInitializer.mainMethod(
              TestAdapterInitializer.ModuleClassName,
              TestAdapterInitializer.MainMethodName
            )
          )
        }
    }

I disagree here. Not running main is indicative of JSExport and the likes I think, not that we want to link with tests (I don't think this is very common). I propose here that we instead add a test flag for the test case and add that same flag to the Commands.Test (it can also be an additional flag in the compileArgs).

(state, result)
}
case Jvm(_, _, _, _, _, _) =>
compileProjects(userProjects :: Nil, state, compileArgs, originId, logger)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Alternatively here we can do the same as in the interpreter when it hits a JVM project on link. I'm not sure if there is much difference.

@tgodzik
Copy link
Contributor

tgodzik commented Dec 14, 2024

@tgodzik I'm not quite sure where/how I can write a test for this. I looked at BspCompileSpec, but I'm not quite sure how I can pass in the compileArgs there.

This should be similar to 32dbfb1#diff-9458dec34378d1535538ca59de86e441bcef70b5739a0cc484281ac8ed1ef1aaR695

I also want to further investigate some bugs with the JS linker config as it does not seem to respect everything that is set from bloop-config, but maybe that should be it's own PR?

Would probably be easier to review, but I can also ask for help reviewing

@tgodzik
Copy link
Contributor

tgodzik commented Dec 14, 2024

I disagree here. Not running main is indicative of JSExport and the likes I think, not that we want to link with tests (I don't think this is very common). I propose here that we instead add a test flag for the test case and add that same flag to the Commands.Test (it can also be an additional flag in the compileArgs).

I haven't worked in that part too much, but I think that might have been a simplification since Bloop wasn't used for publishing anything

- Added better differentiation of what is a test task

- Makes sure that compile is run before link (compiles are properly
  cached).

- Linking now properly works for projects that use JSExport instead of a
  main class
case Success(linkRunsSeq) =>
linkRunsSeq
.map(_._2)
.foldLeft(Option.empty[CompileResult])((_, res) => // TODO propogate stuff better here
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This logic is somewhat lifted from the test function, but it doesn't really work that well here. I'm not entirely sure what the best strategy is for combining the compileresults (actually linker results)

@KristianAN
Copy link
Contributor Author

So far I have tested that the changes in this PR allows for linking using JSExport and with a main method without having to call run.

This is also tested through BSP in relation to this PR: oyvindberg/bleep#445.

I have also added support for the new minification in ScalaJS which is now set to true for release esmodule builds. We can add an option for this in bloop-config so that users can override this setting if they want.

Further TODOs for this PR:

  • Fix the TODO I have commented
  • Add some tests
  • Look at the ScalaNative linker

@tgodzik anything else you think should be done?

@tgodzik
Copy link
Contributor

tgodzik commented Dec 16, 2024

anything else you think should be done?

Nothing comes to mind, but maybe @sjrd will be able to take a quick look here?

Removes the dependency on frontend in jsbridge to avoid circular dependency when
publishing bridge before running tests in frontend.

The only reason jsbridge dependend on frontend was for logging the project name
in jsbridge1, for jsbridge06 only the project name was needed for setting the
output dir. frontend was also needed for running the tests for the bridge.
Moving the bride test to it's own module let's us keep the dependecy graph neat
and tidy.
…c as ScalaJS

This uses the native buildTarget to check if we have an application or a dynamic
library or a static library. If it is not an application we do not set the main
class.

Also inclued are changes to the frontend to allow for linking without a
specified main class
@KristianAN
Copy link
Contributor Author

@tgodzik this is now blocked by scalacenter/bloop-config#102

@tgodzik
Copy link
Contributor

tgodzik commented Dec 18, 2024

When linking a JVM project the link task now simply noops. This lets
clients send all build projects to the link task and the task will only
link where possible. This way the client does not need to be explicit
when linking (although still possible).

Errors when linking are now accumulated and error logged. Bsp returns an
error code when encountering linking errors.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Moving the these tests to a different module lets us remove the frontend dependency from the bridges. This allows us to create working tests for bsp in the bridges. However it breaks the ToolchainSpecs because the loading the bridge toolchain with the classloader no longer works.

I am no guru on these classloader issues. This is the part of the test that no longer works when separating the modules.

  val state0: State = {
    def setUpScalajs(p: Project): Project = {
      val platform = p.platform match {
        case jsPlatform: Platform.Js =>
          jsPlatform.copy(toolchain = Some(ScalaJsToolchain.apply(this.getClass.getClassLoader)))
        case _ => p.platform
      }
      p.copy(platform = platform)
    }

    val configDir = TestUtil.getBloopConfigDir("cross-test-build-scalajs-1.x")
    val logger = bloop.logging.BloopLogger.default(configDir.toString())
    TestUtil.loadTestProject(configDir, logger, true, _.map(setUpScalajs))
  }

@tgodzik do you have any neat trick to make this work?

Copy link
Contributor

Choose a reason for hiding this comment

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

We could create a UriClassloader with the bridge in 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.

Actually I think the solution here is just to move these tests into frontend

@KristianAN KristianAN marked this pull request as ready for review December 19, 2024 11:13
@KristianAN
Copy link
Contributor Author

KristianAN commented Dec 19, 2024

Chages introduced in PR:

  • new --link flag for bsp compile, which tries to link the given projects, compiles jvm projects like normal if a jvm project is supplied
  • new flags --release and --debug to let the client dynamically set the linker mode without changing the bloop configuration
  • Removed dependency on frontend in all bridges
  • Moved CrossPlatform.scala to shared
  • Moved all bridge tests to frontend
  • frontend local publishes all bridges before running tests
  • New tests for linking projects through bsp
  • New version of bloop-config that allows clients to specify which type of native project is being linked (application, dynamiclibrary, staticlibrary)
  • Changes made to linking logic that now allows for linking without specifying a main class. This is mainly useful for ScalaJS projects with JSExport and for scala-native projects using dynamicLibrary or staticLibrary application type.

Overall This should bring bloop much closer to sbt/mill and the likes with regards to linking capabilities. There is probably still more work we can do to better the client capabilities when it comes to passing configuration, but I think we can add that later. Hopefully this PR will let us see more linking through bloop so we can discover possible bugs etc.

I'm moving this from draft to ready, but we are still blocked on bloop-config.

@KristianAN
Copy link
Contributor Author

One thing I forgot to add is that I have not really done much with the scalajs bridge for 0.6. I have not added tests, or decoupled it from frontend like the others. I highly doubt there are many projects using bloop for such an old version. It should work as it did before however.

This lets the client override the mode without making changes to the
model. This is more in line with how fullLink/fastLink works in sbt.

Using flags through BSP for this lets the client override the mode
without having to change the actual build. This is very useful for this
task.
Copy link
Contributor

@tgodzik tgodzik left a comment

Choose a reason for hiding this comment

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

Looks good! Just two minor comments, otherwise LGTM

build.sbt Outdated Show resolved Hide resolved
- Logs warning when passing both --release and --debug arguments when
linking through BSP compile. When both arguments are passed we simply
ignore them and link with whatever liker mode is defined through
bloop-config

- fixup in build.sbt: remove commented out code
@KristianAN KristianAN requested a review from tgodzik December 30, 2024 18:58
Copy link
Contributor

@tgodzik tgodzik left a comment

Choose a reason for hiding this comment

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

Great work! LGTM

@tgodzik tgodzik merged commit c1ff013 into scalacenter:main Dec 31, 2024
13 checks passed
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.

3 participants