This is an automated email from the ASF dual-hosted git repository. dsmiley pushed a commit to branch branch_10x in repository https://gitbox.apache.org/repos/asf/solr.git
commit 5b87c74fefe872282cbf919cdcb4eea6293a504c Author: David Smiley <[email protected]> AuthorDate: Fri Jul 10 23:21:17 2026 -0400 build: decouple JS API client bundling into an optional module (#4603) The OpenAPI-generated JS client (npm install + babel build + browserify bundle) was embedded directly in :solr:webapp's build.gradle, making that module quietly depend on npm/node for something unrelated to compiling/packaging the webapp itself. Pull it out into its own module, :solr:webapp:js-client, which exposes the built bundle via a `jsClientBundle` configuration that :solr:webapp now consumes as a normal cross-project dependency (mirroring how it already consumes :solr:server's serverLib/solrCore and :solr:api's jsClient outputs), instead of :solr:webapp defining the npm tasks and reaching into its own local task outputs. Also make the JS client bundle optional, mirroring the existing disableUiModule pattern: - settings.gradle: new -PdisableJsClient=true flag (gradle.ext.withJsClient), excluding :solr:webapp:js-client from the build entirely when set. - solr/webapp/build.gradle: the generatedJSClientBundle dependency is only added when the flag is on; the war{} block's from(configurations.generatedJSClientBundle) is left unconditional since an empty configuration is already a safe no-op. Refactor gradle/node.gradle so it no longer hardcodes which projects need node/npm. (cherry picked from commit 255e9e7b85efb801f4dad1c557aaa4ff5d6c4e0e) --- build.gradle | 1 + gradle/node.gradle | 67 ++++++++++--------- settings.gradle | 6 ++ solr/solr-ref-guide/build.gradle | 1 + solr/webapp/build.gradle | 91 ++----------------------- solr/webapp/gradle.lockfile | 2 +- solr/webapp/js-client/build.gradle.kts | 119 +++++++++++++++++++++++++++++++++ solr/webapp/js-client/gradle.lockfile | 5 ++ 8 files changed, 174 insertions(+), 118 deletions(-) diff --git a/build.gradle b/build.gradle index 88acc7403bb..16ecea30b6c 100644 --- a/build.gradle +++ b/build.gradle @@ -132,6 +132,7 @@ ext { // Use gradle flag in root project for further referencing withUiModule = gradle.ext.withUiModule + withJsClient = gradle.ext.withJsClient } // Include smaller chunks configuring dedicated build areas. diff --git a/gradle/node.gradle b/gradle/node.gradle index 0cf7923bac0..5934db87fdd 100644 --- a/gradle/node.gradle +++ b/gradle/node.gradle @@ -15,47 +15,50 @@ * limitations under the License. */ -configure([project(":solr:solr-ref-guide"), project(":solr:webapp")]) { - apply plugin: libs.plugins.nodegradle.node.get().pluginId +// Projects that need node/npm apply the node-gradle plugin themselves; this shared +// config reacts to whichever projects do so, rather than naming them explicitly. +allprojects { + pluginManager.withPlugin(libs.plugins.nodegradle.node.get().pluginId) { - def npmRegistry = "${-> propertyOrEnvOrDefault("solr.npm.registry", "SOLR_NPM_REGISTRY", '')}" - if (!npmRegistry.isEmpty()) { - tasks.npmSetup { - args.addAll(['--registry', npmRegistry]) - } - - afterEvaluate { - tasks.withType(NpmTask).each {npmTask -> - npmTask.environment.put('NPM_CONFIG_REGISTRY', npmRegistry) + def npmRegistry = "${-> propertyOrEnvOrDefault("solr.npm.registry", "SOLR_NPM_REGISTRY", '')}" + if (!npmRegistry.isEmpty()) { + tasks.npmSetup { + args.addAll(['--registry', npmRegistry]) } - tasks.withType(NpxTask).each {npxTask -> - npxTask.environment.put('NPM_CONFIG_REGISTRY', npmRegistry) + + afterEvaluate { + tasks.withType(NpmTask).each {npmTask -> + npmTask.environment.put('NPM_CONFIG_REGISTRY', npmRegistry) + } + tasks.withType(NpxTask).each {npxTask -> + npxTask.environment.put('NPM_CONFIG_REGISTRY', npmRegistry) + } } } - } - project.ext { - nodeProjectDir = layout.projectDirectory.dir(".gradle/node") - } + project.ext { + nodeProjectDir = layout.projectDirectory.dir(".gradle/node") + } - node { - download = true - version = libs.versions.nodejs.get() + node { + download = true + version = libs.versions.nodejs.get() - def nodeDistUrl = "${-> propertyOrEnvOrDefault("solr.node.distUrl", "SOLR_NODE_DIST_URL", '')}" - if (!nodeDistUrl.isEmpty()) { - distBaseUrl = nodeDistUrl - } + def nodeDistUrl = "${-> propertyOrEnvOrDefault("solr.node.distUrl", "SOLR_NODE_DIST_URL", '')}" + if (!nodeDistUrl.isEmpty()) { + distBaseUrl = nodeDistUrl + } - // The directory where Node.js is unpacked (when download is true) - workDir = file("${project.ext.nodeProjectDir.getAsFile().path}/nodejs") + // The directory where Node.js is unpacked (when download is true) + workDir = file("${project.ext.nodeProjectDir.getAsFile().path}/nodejs") - // The directory where npm is installed (when a specific version is defined) - npmWorkDir = file("${project.ext.nodeProjectDir.getAsFile().path}/npm") + // The directory where npm is installed (when a specific version is defined) + npmWorkDir = file("${project.ext.nodeProjectDir.getAsFile().path}/npm") - // The Node.js project directory location - // This is where the package.json file and node_modules directory are located - // By default it is at the root of the current project - nodeProjectDir = project.ext.nodeProjectDir + // The Node.js project directory location + // This is where the package.json file and node_modules directory are located + // By default it is at the root of the current project + nodeProjectDir = project.ext.nodeProjectDir + } } } diff --git a/settings.gradle b/settings.gradle index 782edec4325..39f8609f636 100644 --- a/settings.gradle +++ b/settings.gradle @@ -74,5 +74,11 @@ if (gradle.ext.withUiModule) { include(":solr:ui") } +def disableJsClientValue = providers.gradleProperty('disableJsClient').orNull +gradle.ext.withJsClient = disableJsClientValue == null || disableJsClientValue != 'true' +if (gradle.ext.withJsClient) { + include(":solr:webapp:js-client") +} + // Configures development for joint Lucene/ Solr composite build. apply from: file('gradle/lucene-dev/lucene-dev-repo-composite.gradle') diff --git a/solr/solr-ref-guide/build.gradle b/solr/solr-ref-guide/build.gradle index 66f4437249b..cf95613fecb 100644 --- a/solr/solr-ref-guide/build.gradle +++ b/solr/solr-ref-guide/build.gradle @@ -19,6 +19,7 @@ import groovy.json.JsonOutput import org.apache.tools.ant.util.TeeOutputStream apply plugin: 'java' +apply plugin: libs.plugins.nodegradle.node.get().pluginId description = 'Solr Reference Guide' diff --git a/solr/webapp/build.gradle b/solr/webapp/build.gradle index 62d6a130562..6ca3fcc6175 100644 --- a/solr/webapp/build.gradle +++ b/solr/webapp/build.gradle @@ -26,14 +26,10 @@ configurations { war {} serverLib solrCore - generatedJSClient generatedJSClientBundle -} - -ext { - jsClientWorkspace = layout.buildDirectory.dir("jsClientWorkspace").get() - jsClientBuildDir = layout.buildDirectory.dir("jsClientBuild").get() - jsClientBundleDir = layout.buildDirectory.dir("jsClientBundle").get() + // TODO: :solr:ui's dev/prod distributions are pulled in via raw cross-project task + // references (see generateUiDevFiles/generateUiProdFiles below) instead of a + // configuration like this one; consider exposing them as a real configuration too. } dependencies { @@ -42,57 +38,11 @@ dependencies { solrCore project(":solr:core") implementation(configurations.solrCore - configurations.serverLib) - generatedJSClient project(path: ":solr:api", configuration: "jsClient") - generatedJSClientBundle files(jsClientBundleDir) { - builtBy "finalizeJsBundleDir" - } -} - -task syncJSClientSourceCode(type: Sync) { - group = 'Solr JS Client' - from configurations.generatedJSClient - - into jsClientWorkspace - - // Keep the node modules, so that they don't need to be re-downloaded - preserve { - include "node_modules/**" + if (gradle.ext.withJsClient) { + generatedJSClientBundle project(path: ":solr:webapp:js-client", configuration: "jsClientBundle") } } -task jsClientDownloadDeps(type: NpmTask) { - group = 'Solr JS Client' - dependsOn tasks.syncJSClientSourceCode - - args = ["install"] - workingDir = file(jsClientWorkspace) - - inputs.dir("${jsClientWorkspace}/src") - inputs.file("${jsClientWorkspace}/package.json") - outputs.dir("${jsClientWorkspace}/node_modules") -} - -task jsClientBuild(type: NpmTask) { - group = 'Solr JS Client' - dependsOn tasks.jsClientDownloadDeps - - args = ["run", "build"] - workingDir = file(jsClientWorkspace) - - inputs.dir("${jsClientWorkspace}/src") - inputs.file("${jsClientWorkspace}/package.json") - inputs.dir("${jsClientWorkspace}/node_modules") - outputs.dir("${jsClientWorkspace}/dist") -} - -task downloadBrowserify(type: NpmTask) { - group = 'Build Dependency Download' - args = ["install", "browserify@${libs.versions.browserify.get()}"] - - inputs.property("browserify version", libs.versions.browserify.get()) - outputs.dir("${nodeProjectDir}/node_modules/browserify") -} - if (gradle.ext.withUiModule) { tasks.register("generateUiDevFiles") { description = "Generate new UI for development and add files to outputs for later referencing." @@ -113,39 +63,10 @@ if (gradle.ext.withUiModule) { } } -task generateJsClientBundle(type: NpxTask) { - group = 'Solr JS Client' - dependsOn tasks.downloadBrowserify - dependsOn tasks.jsClientBuild - - command = 'browserify' - args = ['dist/index.js', '-s', 'solrApi', '-o', "${jsClientBuildDir}/index.js"] - workingDir = file(jsClientWorkspace) - - inputs.dir(jsClientWorkspace) - inputs.property("browserify version", libs.versions.browserify.get()) - - outputs.file("${jsClientBuildDir}/index.js") -} - -task finalizeJsBundleDir(type: Sync) { - group = 'Solr JS Client' - - from configurations.generatedJSClient { - include "README.md" - include "docs/**" - } - - from tasks.generateJsClientBundle { - include "index.js" - } - - into jsClientBundleDir -} - war { from("web") + // note: nonetheless may be disabled, copying nothing from(configurations.generatedJSClientBundle, { into "libs/solr" }) diff --git a/solr/webapp/gradle.lockfile b/solr/webapp/gradle.lockfile index 3e533f21531..27fcf5b4c1b 100644 --- a/solr/webapp/gradle.lockfile +++ b/solr/webapp/gradle.lockfile @@ -159,4 +159,4 @@ org.slf4j:jcl-over-slf4j:2.0.17=serverLib,solrCore org.slf4j:jul-to-slf4j:2.0.17=serverLib org.slf4j:slf4j-api:2.0.17=serverLib,solrCore org.xerial.snappy:snappy-java:1.1.10.8=solrCore -empty=compileClasspath,generatedJSClient,generatedJSClientBundle,jarValidation,missingdoclet,providedCompile,providedRuntime,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,war +empty=compileClasspath,generatedJSClientBundle,jarValidation,missingdoclet,providedCompile,providedRuntime,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,war diff --git a/solr/webapp/js-client/build.gradle.kts b/solr/webapp/js-client/build.gradle.kts new file mode 100644 index 00000000000..1ce43c25978 --- /dev/null +++ b/solr/webapp/js-client/build.gradle.kts @@ -0,0 +1,119 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import com.github.gradle.node.npm.task.NpmTask +import com.github.gradle.node.npm.task.NpxTask + +// Builds the OpenAPI-generated JS client (from :solr:api) into a single bundled +// file, for :solr:webapp to include in the war. This is the only place in the +// build that needs npm/node for this purpose; it can be disabled entirely via +// -PdisableJsClient=true (see settings.gradle). + +plugins { + id("base") + alias(libs.plugins.nodegradle.node) +} + +description = "Generates a JavaScript client for Solr OpenApi" + +val jsClientWorkspace = layout.buildDirectory.dir("jsClientWorkspace").get().asFile +val jsClientBuildDir = layout.buildDirectory.dir("jsClientBuild").get().asFile +val jsClientBundleDir = layout.buildDirectory.dir("jsClientBundle").get().asFile + +val generatedJSClient = configurations.create("generatedJSClient") +val jsClientBundle = configurations.create("jsClientBundle") { + isCanBeConsumed = true + isCanBeResolved = false +} + +dependencies { + generatedJSClient(project(path = ":solr:api", configuration = "jsClient")) +} + +val syncJSClientSourceCode = tasks.register<Sync>("syncJSClientSourceCode") { + from(generatedJSClient) + + into(jsClientWorkspace) + + // Keep the node modules, so that they don't need to be re-downloaded + preserve { + include("node_modules/**") + } +} + +val jsClientDownloadDeps = tasks.register<NpmTask>("jsClientDownloadDeps") { + dependsOn(syncJSClientSourceCode) + + args.set(listOf("install")) + workingDir.set(jsClientWorkspace) + + inputs.dir("$jsClientWorkspace/src") + inputs.file("$jsClientWorkspace/package.json") + outputs.dir("$jsClientWorkspace/node_modules") +} + +val jsClientBuild = tasks.register<NpmTask>("jsClientBuild") { + dependsOn(jsClientDownloadDeps) + + args.set(listOf("run", "build")) + workingDir.set(jsClientWorkspace) + + inputs.dir("$jsClientWorkspace/src") + inputs.file("$jsClientWorkspace/package.json") + inputs.dir("$jsClientWorkspace/node_modules") + outputs.dir("$jsClientWorkspace/dist") +} + +val downloadBrowserify = tasks.register<NpmTask>("downloadBrowserify") { + args.set(listOf("install", "browserify@${libs.versions.browserify.get()}")) + + inputs.property("browserify version", libs.versions.browserify.get()) + outputs.dir(project.extra["nodeProjectDir"].toString() + "/node_modules/browserify") +} + +val generateJsClientBundle = tasks.register<NpxTask>("generateJsClientBundle") { + dependsOn(downloadBrowserify) + dependsOn(jsClientBuild) + + command.set("browserify") + args.set(listOf("dist/index.js", "-s", "solrApi", "-o", "$jsClientBuildDir/index.js")) + workingDir.set(jsClientWorkspace) + + inputs.dir(jsClientWorkspace) + inputs.property("browserify version", libs.versions.browserify.get()) + + outputs.file("$jsClientBuildDir/index.js") +} + +val finalizeJsBundleDir = tasks.register<Sync>("finalizeJsBundleDir") { + from(generatedJSClient) { + include("README.md") + include("docs/**") + } + + from(generateJsClientBundle) { + include("index.js") + } + + into(jsClientBundleDir) +} + +artifacts { + add("jsClientBundle", jsClientBundleDir) { + builtBy(finalizeJsBundleDir) + } +} diff --git a/solr/webapp/js-client/gradle.lockfile b/solr/webapp/js-client/gradle.lockfile new file mode 100644 index 00000000000..3a4ae5a9eaa --- /dev/null +++ b/solr/webapp/js-client/gradle.lockfile @@ -0,0 +1,5 @@ +# This is a Gradle generated file for dependency locking. +# Manual edits can break the build and are not advised. +# This file is expected to be part of source control. +# To regenerate this file, run: ./gradlew :solr:webapp:js-client:dependencies --write-locks +empty=generatedJSClient,jarValidation
