-
Notifications
You must be signed in to change notification settings - Fork 396
Split the scalalib .sjsir files in a separate artifact scalajs-scalalib. #4913
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
Conversation
This was supposed to be a follow-up to #4787. But I realized, after careful consideration of the various dependency scenarios, that the presence of This allows us to setup our artifacts in a way that will allow forward-binary-incompatible changes to @gzm0 Does that seem like a reasonable solution? |
FYI, I'll be on vacation for two weeks starting Saturday. Do we need this to be looked at before that or can it wait? (I might have time, but I can't commit). |
It can wait. If you can provide your opinion on the cyclic-but-not-cyclic dependency between |
Previously, the `.sjsir` files of the scalalib (and libraryAux) were bundled inside scalajs-library.jar. With the plan to drop forward binary compatibility of the upstream scala-library.jar, this model will not work anymore. We now publish those `.sjsir` files in their own artifact `scalajs-scalalib.jar`. It is versioned *both* with the Scala version number and the Scala.js version number, in a way that will allow Ivy resolution to pick the right one. At the POM level, `scalajs-scalalib` depends on `scalajs-javalib`. `scalajs-library` depends on the other two. However, in terms of *actual* content dependencies, as is, `scalajs-scalalib` also depends on `scalajs-library`. If the former is present on a classpath but not (a recent enough version of) the latter, linking errors can appear. This should not be an issue because any real build that depends on `scalajs-scalalib` will also depend on `scalajs-library`. Moreover, if a more recent `scalajs-scalalib` is picked up by Ivy resolution that implicitly depends on a more recent `scalajs-library`, the library that introduces that dependency would also *explicitly* depend on the more recent `scalajs-library`, and so the latter would also be picked up by Ivy resolution. The sbt plugin explicitly adds a dependency on the `scalajs-scalalib` with a Scala/Scala.js version combination that matches `scalaVersion` and `scalaJSVersion`. This way, if it uses a Scala.js version that was built for Scala 2.13.12 but it itself uses Scala 2.13.15, it will get the back-published `scalajs-library` built for Scala 2.13.15.
c68626c
to
b50df89
Compare
I think overall such a cyclic-but-not-cyclic approach is acceptable. What I'm wondering is if it wouldn't make sense to have the dependency the other way around:
With the proposed approach, it is not clear to me which Scala version of What I'm unsure about with this approach is how it looks in eviction scenarios across the version boundary where we split the artifacts. |
For that, I think whatever the approach we take, we must have that If it contains the scalalib, then its version number must be 2.13.12+1.15.0 (that's the only scheme I can think of that will resolve to the right binary API of the scalalib contents). Although it technically works, it seems weird to me that we would jump from Other than for the transition, as long as the thing that contains the scalalib is versioned v2.13.12+1.15.0 and the thing that contains So that leaves me to the question: why do you think scalalib-depends-on-scalajs makes more sense? To me it seems that the converse is less weird. For example IIUC it's because of your comment:
which is of course not obvious from the public version numbers, but that's not different from any other Scala library: it does not expose in its version number (only in its POM, which we would do) the specific patch version of |
Ping @gzm0 :) |
Eviction ScenariosConsider the following project and dependency versions:
This makes the assumption that compiling with an older Scala patch version than an upstream dependency is allowed. Note that the equivalent for Scala.js versions (compile & link with 1.15.0 using lib built with 1.15.1) is allowed. IIUC, this will not resolve scalajs-scalalib correctly (should be Or is the intent that we allow this and ensure that scalajs-library can work with older scalajs-scalalib versions (IMO that's quite prohibitive). This also somewhat raises the question: Why do we even need to separate the scalajs-library / scalajs-scalalib artifacts? What happens if we just version Dependency orderFrom an actual dependency POV, you are 100% right. And this is probably the stronger argument: If we ever intend to go through with #4787, we need it in that order. My concern was about In graphs, for the current scheme this would look like this: graph LR
jl["scalajs-javalib@1.15.0"]
sjs["scalajs-library_2.13@1.15.0"]
s11["scalajs-scalalib_2.13@2.13.11+1.15.0"]
s12["scalajs-scalalib_2.13@2.13.12+1.15.0"]
s13["scalajs-scalalib_2.13@2.13.13+1.15.0"]
m11["?"] -.-> s11 --> jl
sjs --> s12 --> jl
sjs --> jl
m13["?"] -.-> s13 --> jl
If we inverted scalajs-library and scalajs-scalalib, it would look like this: graph LR
jl["scalajs-javalib@1.15.0"]
sjs["scalajs-library_2.13@1.15.0"]
s11["scalajs-scalalib_2.13@2.13.11+1.15.0"]
s12["scalajs-scalalib_2.13@2.13.12+1.15.0"]
s13["scalajs-scalalib_2.13@2.13.13+1.15.0"]
sjs --> jl
s11 --> sjs
s11 --> jl
s12 --> sjs
s12 --> jl
s13 --> sjs
s13 --> jl
|
That is the intent, yes. But it's not an older scalajs-scalalib. It's a scalajs-scalalib for a newer Scala version built by an older Scala.js. That is perfectly fine and not constraining: the API that we can use is anyway that advertised by (the
That would be problematic for a third-party library that was built with 2.13.4+1.16.0, relies on an API added and provided by 1.16.0 in |
I'm afraid I fail to see what's problematic about that. If nothing depends on them, they won't even appear in the resolution, will they? I think I don't understand what you mean by "internal" here.
Just to be clear: AFAICT that specific variant with the artifacts named like that is not realistic from a 1.14->1.15 transition point of view. We need the "outermost" artifacts to be named That said, couldn't you make the same argument that you're doing for the first graph, about a |
OK. I understand that this will not cause any issues for interface compatibility. But what about code in overrides? If the "Project" in the above example depends on a fix to an override that was made in Scala.js 1.15.1, that's a problem, no? IIUC pretty much the sole purpose of having the Scala.js version in the scalajs-scalalib artifact is so that we can publish a new one if there is an issue with the Scala.js side (override or compiler bug)?
There is no issue for dependency resolution. My initial feeling was just that the global dependency graph is "odd" because there are artifacts that will only be depended upon when evictions happen (in the example graph, when a SJS 1.15.0 user targets Scala 2.13.13, in fact, IIUC the 2.13.11 artifact seems pointless?). |
Yes, that could be a problem. Not sure if there is a way out of that. At least if it happens, it's always fixable by using a recent enough Scala.js in the final project (the one that we actually link).
It's more important than that. We need that to be able to make any kind of change in scalalib after the release of 1.15.0. For example, if the latest Scala version at the time of 1.15.0 is 2.13.14. Then we will publish a given
Ah yes, I see. It's not that some can only surface through evictions (that's not possible by Ivy rules), but there are some that will always be evicted by something else. That might be a bit odd, yes. I would still consider it an improvement over the current status quo where this always happens, just silently without any graph to make it explicit (-ly odd), no? |
It seems like there won't, unless the dependency resolution system is able to calculate "new" artifact versions. (combine the bigger Scala version with the bigger Scala.js version). This is likely not something we can expect from it. Sorry I didn't catch on to this earlier, somehow I thought this is possible (when we had the original discussions in the SIP).
You mean Scala, not Scala.js? In the example above, the final project uses the latest Scala.js version, but not the latest Scala version. Does the Scala library offer backwards source compatibility? If no (which I'd expect), this can be a real problem, no? We essentially force downstream projects to update for versions where we promise(d) that it is not necessary. |
Actually both Scala and Scala.js. So that we introduce a dependency on An alternative is to directly add it as a
The Scala library won't promise backwards source compatibility given SIP-51, no. However, on that particular point, the most recent analysis of SIP-51 for the JVM suggests that even Scala/JVM will require this. To be more precise, if a project transitively depends on |
OK, in the case where we require the project to have a Scala version that is at least as new as the transitive dependencies, the above scenario cannot happen. What can happen is this:
Basically, a Scala version upgrade can break something in a dependency that was relying on a bugfix introduced in a later Scala.js version. But I think at that point, this is indistinguishable from a bug introduced in the new Scala library. So we can probably live with it. So for me the only question remains how do we ensure the Scala library version dependency is enforced / checked? |
The relationship between Our sbt plugin, with this PR, adds Combining the above two means that we get the enforcement that Is that what you were asking? |
Yes. So at this point, I'm convinced that going the way of this PR is OK. What I'm wondering is if we should actually merge / release this now (when the build tools do not enforce the scala version thingy yet). Then again, a build tool author will say the same about Scala.js publishing the relevant artifacts 🤷 So probably we should just go for it. Maybe, as a stop-gap, we can add the check for the version ourselves, but doing this correctly (considering older versions in a backwards compatible manner) is likely not trivial. I'll give this another thorough review for good measure, but I don't expect any issues. |
Thanks for the thorough analysis and questioning. This stuff is hard. |
When using Scala.js 1.15.0+ together with Scala 2, we now add an explicit dependency to the `scalajs-scalalib` artifact. That artifact comes with Scala.js 1.15.0+, and is double-versioned with the Scala version and the Scala.js version. As SIP-51 gets implemented and future versions of the Scala library may break forward binary compatibility, the new `scalajs-scalalib` will be able to bring in new APIs back to existing Scala.js versions. This commit mimics the changes done to sbt-scalajs in scala-js/scala-js#4913 Pull request: #2988
Previously, the
.sjsir
files of the scalalib (and libraryAux) were bundled inside scalajs-library.jar. With the plan to drop forward binary compatibility of the upstream scala-library.jar, this model will not work anymore.We now publish those
.sjsir
files in their own artifactscalajs-scalalib.jar
. It is versioned both with the Scala version number and the Scala.js version number, in a way that will allow Ivy resolution to pick the right one.At the POM level,
scalajs-scalalib
depends onscalajs-javalib
.scalajs-library
depends on the other two. However, in terms of actual content dependencies, as is,scalajs-scalalib
also depends onscalajs-library
.If the former is present on a classpath but not (a recent enough version of) the latter, linking errors can appear. This should not be an issue because any real build that depends on
scalajs-scalalib
will also depend onscalajs-library
. Moreover, if a more recentscalajs-scalalib
is picked up by Ivy resolution that implicitly depends on a more recentscalajs-library
, the library that introduces that dependency would also explicitly depend on the more recentscalajs-library
, and so the latter would also be picked up by Ivy resolution.The sbt plugin explicitly adds a dependency on the
scalajs-scalalib
with a Scala/Scala.js version combination that matchesscalaVersion
andscalaJSVersion
. This way, if it uses a Scala.js version that was built for Scala 2.13.12 but it itself uses Scala 2.13.15, it will get the back-publishedscalajs-library
built for Scala 2.13.15.