-
Notifications
You must be signed in to change notification settings - Fork 37
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
Clean production classpath #90
base: main
Are you sure you want to change the base?
Conversation
Just wanted to understand more about the context of the issue. Is the gwt-dev jar included in a deployable server application? Just wondering, with the current version, are you able to exclude gwt-dev from the runtime in your project gradle build script? for example:
It seems a good idea to use separate dependency scopes to solve this runtime dependency issue. Also, would it be easy to arrange a minimum example to test the behaviour and gradle versions? |
Sure!
It has been included in our WAR-file for years, yes. And it seems to be a habbit in other projects, too. For example, we have dependencies on some GWT-libraries like But due to the sad list of vulns, we were thinking whether we really need it or any of its deps. So I've been doing some preliminary testing using the patched
I will try and let you know how far I can get with this approach. Until then, happy new year! |
I've found some references. The most compelling source is a mailing list post from Oct 2024 by Colin Alworth, the main contributor to GWT. The dependencies are also mentioned in Deploying on a servlet container using RPC and Deploying RPC. You can ignore the text about the Apache Ant My interpretation is, that
So, not even
This didn't really work, because it removes If I would take all this to the extreme, it might look like this:
|
The Yes, I suppose most GWT use cases are limited to the frontend, but Your suggestion looks great. I think we can configure |
If GWT RPC is not being used, it's a good idea to completely separate the frontend from the backend code. For example, only deploy the compiled JS and related files to a location where it can work seamlessly with the backend. In this way, the backend will never mess up with the dependencies of the frontend. But for legacy projects which have GWT code sitting together with the server side code, it'd be great to have fine control over the GWT dependencies in this plugin. |
During development you need
Non of these should ever be deployed in a If needed, you only need to deploy (and their transitive dependencies):
If you create a gradle project with just a single module that combines GWT and web application then you have the above problem, because now you only have a single classpath for GWT compilation and for your web application. E.g. your
In that case you would need a dedicated configuration, e.g. named A better setup is to split that single gradle module into two gradle modules, e.g. Once you have two gradle modules you need to figure out a way to share the gwt compiler outputs of multi-module-project/settings.gradle:rootProject.name = 'multi-module-project'
include ':gwt-ui'
include ':webapp' multi-module-project/gwt-ui/build.gradle:plugins {
id 'java'
id "org.docstr.gwt" version "2.1.6"
}
group = 'com.example.gwt'
version = '1.0-SNAPSHOT'
repositories {
mavenCentral()
}
dependencies {
// your GWT dependencies, e.g. guava-gwt
// implementation "..."
}
gwt {
modules = ['com.example.gwt.GwtUi']
// Relocate some GWT outputs so they each have a dedicated folder.
// This works better when sharing the GWT output with other projects because
// you can precisely control what to share.
// The final JS App
war = file("${layout.buildDirectory}/gwt/js")
// Server side GWT data, e.g. symbol maps, sourcemaps
deploy = file("${layout.buildDirectory}/gwt/deploy")
// Source files generated by GWT compiler
gen = file("${layout.buildDirectory}/gwt/gen")
// Compile report as information for the GWT developer
extra = file("${layout.buildDirectory}/gwt/extra")
}
// Additional configurations each holding a single zip file with
// the corresponding GWT compiler output.
configurations {
gwtJsZipFile
gwtDeployZipFile
}
// Zip task to package GWT JS output
tasks.register("zipGwtJs", Zip) {
dependsOn("gwtCompile")
from (gwt.war)
destinationDirectory = layout.buildDirectory.dir('gwt')
archiveBaseName = "${project.name}-js"
}
// Zip task to package GWT deploy output
tasks.register("zipGwtDeploy", Zip) {
dependsOn("gwtCompile")
from (gwt.deploy)
destinationDirectory = layout.buildDirectory.dir('gwt')
archiveBaseName = "${project.name}-deploy"
}
// Add each zip file as artifact to its corresponding configuration.
// These can then be consumed by other projects.
artifacts {
gwtJsZipFile tasks.named("zipGwtJs")
gwtDeployZipFile tasks.named("zipGwtDeploy")
} multi-module-project/webapp/build.gradle:plugins {
id 'java'
id 'war'
}
group = 'com.example.gwt'
version = '1.0-SNAPSHOT'
repositories {
mavenCentral()
}
// Additional configurations that each will hold a single zip file
// with the corresponding GWT compiler output
configurations {
gwtJsZipFile
gwtDeployZipFile
}
dependencies {
// web app dependencies
compileOnly 'javax.servlet:javax.servlet-api:3.1.0'
// Depend on the zip files produced by 'gwt-ui' module by depending
// on the corresponding configurations.
// These should be unzipped into the final war file
gwtJsZipFile project(path: ':gwt-ui', configuration: 'gwtJsZipFile')
gwtDeployZipFile project(path: ':gwt-ui', configuration: 'gwtDeployZipFile')
}
war {
// Ensure that 'gwt-ui' has been compiled and zip files are ready
dependsOn(configurations.gwtJsZipFile)
dependsOn(configurations.gwtDeployZipFile)
// include the JS app to the root of the war file
into ('/') {
from zipTree(configurations.gwtJsZipFile.singleFile)
}
// include the deploy files to WEB-INF/deploy as they are only used by the server,
// e.g. during client exception stack trace deobfuscation (if the JS app sends the
// stack trace to the server)
into ('WEB-INF/deploy') {
from zipTree(configurations.gwtDeployZipFile.singleFile)
}
} With the above the final |
Thank you @jnehlmeier for a comprehensive example. So the original issue (gwt-dev in prod classpath) is avoidable with the current version of this plugin by splitting our app into multiple Gradle modules. I will need to bring this up as an option to my team. As a bit of a sidetrack. @jiakuan you asked about testing Gradle versions. See commit 37896d0d in my clone. I didn't, yet, create a PR from it. I don't know how you feel about exploding your CI traffic or cache size: the test I modified downloads and caches all those Gradle versions mentioned in there. |
@jnehlmeier Great explanation and examples! If you don’t mind, I’m going to move this content into a separate document and make some adjustments. I’ve been using the exact same multi-module setup, and we haven’t encountered the dependency issues mentioned above — though we do configure the war output directory directly, something like:
@vmj As long as GitHub Actions can handle it, automating compatibility tests for the latest five to ten major versions of Gradle would be great. The CI execution time should be fine, and we could run these checks in a separate GitHub workflow. However, if it becomes too cumbersome, we can simply note in the documentation that certain Gradle versions may not work, and encourage users to report any related issues. Because Gradle updates so quickly, it’s not always feasible to cover every version in our tests. Thoughts? If you’d like, you can commit these changes to this PR and we can test them out. Regarding this PR: Question: |
@jiakuan Sure go ahead. The only inconvenience I encountered while creating the example was that I had to reconfigure the default It seems like that
In order to easily zip the JS output without Maybe changing the default would make sense. Also The example assumes that you will deploy the file |
… Servlet API, Test Suites)
The GwtMockitoRunner based tests, though, do have a runtime dependency on it (but not compile time).
f638e3a
to
d7cb1f9
Compare
I have updated the code:
I added a I'm still adding the GWT dev et al to a single dependency scope shared by And naturally I broke the |
Good idea! I've adjusted the default output directories and created a pull request. |
Unfortunately I haven't had time to revisit this for a week. And I doubt I will have time for another week. If anyone wants to take a look at the I should get back to this eventually, though. |
NOTE: This is probably a breaking change for some users. Also, this is using incubating Gradle APIs. I just wanted to create a POC and get your opinnion on this whole thing.
gwt-dev
brings 55 additional JAR files with it. 32 of them contain reported vulnerabilities, totaling at 246 CVEs. The last GWT release (2.11 -> 2.12.1) did not update a single one of these dependencies. (For details, see https://github.com/vmj/gwt-dev-vulnerabilities)In my project, virtually all of those transitive dependencies are not actually used at production runtime.
gwt-user
is what we need.Because I have hard time proving to my clients that they can ignore those CVEs, I created this patch. It moves the gwt-dev and gwt-codeserver to a separate dependency scope, which is then added where needed. That is,
gwtCompile
,gwtDevMode
,gwtSuperDev
and anyTest
tasks that requested GWT capabilities.If you agree that this would be good way to go, let me know. I can try to find a non-incubating way of doing the same. I'm not sure what to do to the "breaking change" part, though.