diff --git a/src/cli/install-tool/index.ts b/src/cli/install-tool/index.ts index e9fcb792e..0ffdf2b80 100644 --- a/src/cli/install-tool/index.ts +++ b/src/cli/install-tool/index.ts @@ -13,6 +13,10 @@ import { InstallJavaJreService, InstallJavaService, } from '../tools/java'; +import { + GradleVersionResolver, + InstallGradleService, +} from '../tools/java/gradle'; import { InstallMavenService } from '../tools/java/maven'; import { JavaJdkVersionResolver, @@ -60,6 +64,7 @@ function prepareInstallContainer(): Container { container.bind(INSTALL_TOOL_TOKEN).to(InstallDotnetService); container.bind(INSTALL_TOOL_TOKEN).to(InstallFlutterService); container.bind(INSTALL_TOOL_TOKEN).to(InstallFluxService); + container.bind(INSTALL_TOOL_TOKEN).to(InstallGradleService); container.bind(INSTALL_TOOL_TOKEN).to(InstallJavaService); container.bind(INSTALL_TOOL_TOKEN).to(InstallJavaJreService); container.bind(INSTALL_TOOL_TOKEN).to(InstallJavaJdkService); @@ -82,10 +87,11 @@ function prepareResolveContainer(): Container { container.bind(ToolVersionResolverService).toSelf(); // tool version resolver - container.bind(TOOL_VERSION_RESOLVER).to(NodeVersionResolver); + container.bind(TOOL_VERSION_RESOLVER).to(GradleVersionResolver); container.bind(TOOL_VERSION_RESOLVER).to(JavaVersionResolver); container.bind(TOOL_VERSION_RESOLVER).to(JavaJreVersionResolver); container.bind(TOOL_VERSION_RESOLVER).to(JavaJdkVersionResolver); + container.bind(TOOL_VERSION_RESOLVER).to(NodeVersionResolver); container.bind(TOOL_VERSION_RESOLVER).to(YarnVersionResolver); logger.trace('preparing container done'); diff --git a/src/cli/tools/index.ts b/src/cli/tools/index.ts index ee4da7c9f..93ada5e20 100644 --- a/src/cli/tools/index.ts +++ b/src/cli/tools/index.ts @@ -9,6 +9,7 @@ export const NoPrepareTools = [ 'corepack', 'flux', 'gleam', + 'gradle', 'lerna', 'maven', 'node', diff --git a/src/cli/tools/java/gradle.ts b/src/cli/tools/java/gradle.ts new file mode 100644 index 000000000..57cb5546a --- /dev/null +++ b/src/cli/tools/java/gradle.ts @@ -0,0 +1,89 @@ +import fs from 'node:fs/promises'; +import { join } from 'node:path'; +import is from '@sindresorhus/is'; +import { execa } from 'execa'; +import { inject, injectable } from 'inversify'; +import semver from 'semver'; +import { InstallToolBaseService } from '../../install-tool/install-tool-base.service'; +import { ToolVersionResolver } from '../../install-tool/tool-version-resolver'; +import { + CompressionService, + EnvService, + HttpService, + PathService, +} from '../../services'; +import { GradleVersionData } from './schema'; + +@injectable() +export class InstallGradleService extends InstallToolBaseService { + readonly name = 'gradle'; + + constructor( + @inject(EnvService) envSvc: EnvService, + @inject(PathService) pathSvc: PathService, + @inject(HttpService) private http: HttpService, + @inject(CompressionService) private compress: CompressionService, + ) { + super(pathSvc, envSvc); + } + + override async install(version: string): Promise { + const name = this.name; + const filename = `${name}-${version}-bin.zip`; + const url = `https://services.gradle.org/distributions/${filename}`; + const checksumFileUrl = `${url}.sha256`; + + const expectedChecksum = await this.readChecksum(checksumFileUrl); + const file = await this.http.download({ + url, + checksumType: 'sha256', + expectedChecksum, + }); + + let path = await this.pathSvc.findToolPath(this.name); + if (!path) { + path = await this.pathSvc.createToolPath(this.name); + } + + path = await this.pathSvc.createVersionedToolPath(this.name, version); + + await this.compress.extract({ file, cwd: path, strip: 1 }); + } + + override async link(version: string): Promise { + const src = join(this.pathSvc.versionedToolPath(this.name, version), 'bin'); + await this.shellwrapper({ + srcDir: src, + exports: 'GRADLE_USER_HOME=$HOME/.gradle', + }); + } + + override async test(_version: string): Promise { + await execa('gradle', ['--version'], { + stdio: ['inherit', 'inherit', 1], + }); + } + + override validate(version: string): Promise { + return Promise.resolve(semver.coerce(version) !== null); + } + + private async readChecksum(url: string): Promise { + const checksumFile = await this.http.download({ url }); + return (await fs.readFile(checksumFile, 'utf-8')).split(' ')[0]?.trim(); + } +} + +@injectable() +export class GradleVersionResolver extends ToolVersionResolver { + readonly tool = 'gradle'; + + async resolve(version: string | undefined): Promise { + if (!is.nonEmptyStringAndNotWhitespace(version) || version === 'latest') { + return GradleVersionData.parse( + await this.http.getJson('https://services.gradle.org/versions/current'), + )?.version; + } + return version; + } +} diff --git a/src/cli/tools/java/index.ts b/src/cli/tools/java/index.ts index cb35429a6..2dedb162d 100644 --- a/src/cli/tools/java/index.ts +++ b/src/cli/tools/java/index.ts @@ -12,7 +12,12 @@ import { PathService, } from '../../services'; import { logger } from '../../utils'; -import { resolveJavaDownloadUrl, resolveLatestJavaLtsVersion } from './utils'; +import { + createGradleSettings, + createMavenSettings, + resolveJavaDownloadUrl, + resolveLatestJavaLtsVersion, +} from './utils'; @injectable() export class PrepareJavaService extends PrepareToolBaseService { @@ -36,6 +41,9 @@ export class PrepareJavaService extends PrepareToolBaseService { return; } + await createMavenSettings(this.envSvc.userHome, this.envSvc.userId); + await createGradleSettings(this.envSvc.userHome, this.envSvc.userId); + const version = await resolveLatestJavaLtsVersion( this.httpSvc, 'jre', diff --git a/src/cli/tools/java/schema.ts b/src/cli/tools/java/schema.ts index c88d17333..62cd1f1a1 100644 --- a/src/cli/tools/java/schema.ts +++ b/src/cli/tools/java/schema.ts @@ -30,3 +30,7 @@ const AdoptiumRelease = z.object({ }); export const AdoptiumReleases = z.array(AdoptiumRelease); + +export const GradleVersionData = z.object({ + version: z.string(), +}); diff --git a/src/cli/tools/java/utils.ts b/src/cli/tools/java/utils.ts index d6e29959e..8cd13740e 100644 --- a/src/cli/tools/java/utils.ts +++ b/src/cli/tools/java/utils.ts @@ -1,5 +1,9 @@ +import fs from 'node:fs/promises'; +import path from 'node:path'; +import { codeBlock } from 'common-tags'; +import { execa } from 'execa'; import type { HttpService } from '../../services'; -import type { Arch } from '../../utils'; +import { type Arch, fileExists, logger } from '../../utils'; import { type AdoptiumPackage, AdoptiumReleaseVersions, @@ -42,3 +46,63 @@ export async function resolveJavaDownloadUrl( return res?.[0]?.binaries?.[0]?.package; } + +export async function createMavenSettings( + home: string, + userId: number, +): Promise { + const dir = path.join(home, '.m2'); + const file = path.join(dir, 'settings.xml'); + if (await fileExists(file)) { + logger.debug('Maven settings already found'); + return; + } + logger.debug('Creating Maven settings'); + + await fs.mkdir(dir); + + await fs.writeFile( + file, + codeBlock` + + + + `, + ); + + // fs isn't recursive, so we use system binaries + await execa('chown', ['-R', `${userId}`, dir]); + await execa('chmod', ['-R', 'g+w', dir]); +} + +export async function createGradleSettings( + home: string, + userId: number, +): Promise { + const dir = path.join(home, '.gradle'); + const file = path.join(dir, 'gradle.properties'); + if (await fileExists(file)) { + logger.debug('Gradle settings already found'); + return; + } + logger.debug('Creating Gradle settings'); + + await fs.mkdir(dir); + + await fs.writeFile( + file, + codeBlock` + org.gradle.parallel=true + org.gradle.configureondemand=true + org.gradle.daemon=false + org.gradle.caching=false + `, + ); + + // fs isn't recursive, so we use system binaries + await execa('chown', ['-R', `${userId}`, dir]); + await execa('chmod', ['-R', 'g+w', dir]); +} diff --git a/src/usr/local/containerbase/tools/v2/gradle.sh b/src/usr/local/containerbase/tools/v2/gradle.sh deleted file mode 100644 index cb12c4c44..000000000 --- a/src/usr/local/containerbase/tools/v2/gradle.sh +++ /dev/null @@ -1,50 +0,0 @@ -#!/bin/bash - -function check_tool_requirements () { - check_command java - # TODO: do we still need this ? - if [[ "${TOOL_VERSION}" == "latest" ]]; then - export "TOOL_VERSION=$(curl --retry 3 -sSfL https://services.gradle.org/versions/current | jq --raw-output '.version')" - fi - check_semver "$TOOL_VERSION" minor -} - -function prepare_tool() { - # shellcheck source=/dev/null - . "$(get_containerbase_path)/utils/java.sh" - create_maven_settings - create_gradle_settings - create_tool_path > /dev/null -} - -function install_tool () { - local tool_path - local versioned_tool_path - local file - local URL='https://services.gradle.org/distributions' - - tool_path=$(find_tool_path) - - if [[ ! -d "${tool_path}" ]]; then - if [[ $(is_root) -ne 0 ]]; then - echo "${TOOL_NAME} not prepared" - exit 1 - fi - prepare_tool - tool_path=$(find_tool_path) - fi - - file=$(get_from_url "${URL}/gradle-${TOOL_VERSION}-bin.zip") - - versioned_tool_path=$(create_versioned_tool_path) - bsdtar --strip 1 -C "${versioned_tool_path}" -xf "${file}" -} - -function link_tool () { - local versioned_tool_path - versioned_tool_path=$(find_versioned_tool_path) - - shell_wrapper "${TOOL_NAME}" "${versioned_tool_path}/bin" "GRADLE_USER_HOME=\$HOME/.gradle" - - [[ -n $SKIP_VERSION ]] || gradle --version -} diff --git a/test/java/Dockerfile b/test/java/Dockerfile index e397c4d68..9c5eedaeb 100644 --- a/test/java/Dockerfile +++ b/test/java/Dockerfile @@ -186,6 +186,8 @@ RUN install-tool scala v2.13.14 # renovate: datasource=github-releases packageName=sbt/sbt RUN install-tool sbt v1.10.0 +RUN install-tool gradle 8.8-rc-2 + # doesn't work for arbitrary users USER 1000 @@ -202,6 +204,7 @@ RUN set -ex; \ FROM base as test-latest-version RUN install-tool java-jre +RUN install-tool gradle #--------------------------------------