@@ -189,95 +189,88 @@ jacocoTestCoverageVerification {
189189// ---------------------------------------------------------------------------
190190// Hermetic gitleaks bundling
191191// ---------------------------------------------------------------------------
192- // Detects the current build platform (OS + arch) at Gradle configuration time
193- // and downloads the matching gitleaks binary, storing it as the plain name
194- // "gitleaks" under build/generated-resources/gitleaks/ so it is packed into
195- // the JAR as the classpath resource "gitleaks/gitleaks".
192+ // Downloads gitleaks binaries at build time and packs them into the JAR as
193+ // arch-specific classpath resources so GitleaksRunner can extract the right
194+ // one at runtime without a host installation.
196195//
197- // At runtime, GitleaksRunner extracts it to a JVM temp directory (cleaned up
198- // on JVM shutdown) and invokes it directly — no host installation required.
199- //
200- // If the build platform is not recognised (e.g. Windows), the download is
201- // skipped and a warning is printed. In that case set
202- // commit.secretScanning.scanner-path in git-proxy-local.yml to point at a
203- // locally installed gitleaks binary.
196+ // Default: auto-detects the host OS/arch and downloads only that binary.
197+ // Override via -PgitleaksTargets to download specific platforms:
198+ // -PgitleaksTargets=all → all four supported targets
199+ // -PgitleaksTargets=linux_x64,linux_arm64 → explicit list
200+ // -PgitleaksTargets=linux_x64 → single target (used by Dockerfile)
204201// ---------------------------------------------------------------------------
205202
206- // Resource root: files here appear at the root of the classpath in the JAR.
207- // The gitleaks binary lands at build/generated-resources/gitleaks
208- // and is packed into the JAR as the classpath resource "gitleaks".
209203def gitleaksResourceDir = layout. buildDirectory. dir(" generated-resources" )
210-
211204sourceSets. main. resources. srcDir(gitleaksResourceDir)
212205
213- // Detect build-time OS and CPU architecture to select the right gitleaks tarball
214- def buildOs = System . properties[' os.name' ]. toLowerCase()
215- def buildArch = System . properties[' os.arch' ]. toLowerCase()
206+ def allGitleaksTargets = [' linux_x64' , ' linux_arm64' , ' darwin_x64' , ' darwin_arm64' ]
216207
217- def gitleaksTarSuffix = {
218- String osKey = buildOs. contains(' linux' ) ? ' linux'
219- : (buildOs. contains(' mac' ) || buildOs. contains(' darwin' )) ? ' darwin'
208+ def gitleaksTargets = {
209+ if (project. hasProperty(' gitleaksTargets' )) {
210+ String prop = project. getProperty(' gitleaksTargets' ) as String
211+ return prop. trim() == ' all'
212+ ? allGitleaksTargets
213+ : prop. split(' ,' ). collect { it. trim() }. toList()
214+ }
215+ // Default: detect host platform
216+ String os = System . properties[' os.name' ]. toLowerCase()
217+ String arch = System . properties[' os.arch' ]. toLowerCase()
218+ String osKey = os. contains(' linux' ) ? ' linux'
219+ : (os. contains(' mac' ) || os. contains(' darwin' )) ? ' darwin'
220220 : null
221- String archKey = (buildArch . contains(' amd64' ) || buildArch . contains(' x86_64' )) ? ' x64'
222- : (buildArch . contains(' aarch64' ) || buildArch . contains(' arm64' )) ? ' arm64'
221+ String archKey = (arch . contains(' amd64' ) || arch . contains(' x86_64' )) ? ' x64'
222+ : (arch . contains(' aarch64' ) || arch . contains(' arm64' )) ? ' arm64'
223223 : null
224- (osKey && archKey) ? " ${ osKey} _${ archKey} " : null
224+ if (osKey && archKey) return [" ${ osKey} _${ archKey} " ]
225+ logger. warn(" gitleaks: unrecognised build platform (os='${ os} ', arch='${ arch} ') — " +
226+ " skipping download. Use -PgitleaksTargets=<target> or set scanner-path in config." )
227+ return []
225228}()
226229
227230tasks. register(' downloadGitleaks' ) {
228231 group = ' build setup'
229- description = " Downloads the gitleaks ${ gitleaksVersion} binary for the current build platform ."
232+ description = " Downloads gitleaks ${ gitleaksVersion} binaries for all supported platforms ."
230233
231- // Output: build/generated-resources/gitleaks → JAR classpath: gitleaks
232- def outputFile = gitleaksResourceDir. map { it. file(" gitleaks" ) }
233- outputs . file(outputFile)
234+ gitleaksTargets . each { suffix ->
235+ outputs . file( gitleaksResourceDir. map { it. file(" gitleaks/ ${ suffix } " ) })
236+ }
234237
235238 doLast {
236- if (! gitleaksTarSuffix) {
237- logger. warn(
238- " Unsupported build platform (os='${ buildOs} ', arch='${ buildArch} ') — " +
239- " skipping bundled gitleaks download. Set commit.secretScanning.scanner-path " +
240- " in git-proxy-local.yml to use a locally installed gitleaks binary." )
241- return
242- }
243-
244- def out = outputFile. get(). asFile
245- if (out. exists()) {
246- logger. lifecycle(" gitleaks ${ gitleaksVersion} (${ gitleaksTarSuffix} ) already bundled, skipping." )
247- return
248- }
249- out. parentFile. mkdirs()
250-
251- def tarName = " gitleaks_${ gitleaksVersion} _${ gitleaksTarSuffix} .tar.gz"
252- def gitleaksDownloadUrl = ' https://github.com/gitleaks/gitleaks/releases/download'
253- if (System . getenv(' GITLEAKS_DOWNLOAD_URL' )) {
254- gitleaksDownloadUrl = System . getenv(' GITLEAKS_DOWNLOAD_URL' )
255- }
256- def tarUrl = " ${ gitleaksDownloadUrl} /v${ gitleaksVersion} /${ tarName} "
257- def tmpDir = layout. buildDirectory. dir(" tmp/gitleaks" ). get(). asFile
239+ def baseDownloadUrl = System . getenv(' GITLEAKS_DOWNLOAD_URL' )
240+ ?: ' https://github.com/gitleaks/gitleaks/releases/download'
241+ def tmpDir = layout. buildDirectory. dir(" tmp/gitleaks" ). get(). asFile
258242 tmpDir. mkdirs()
259- def tarFile = new File (tmpDir, tarName)
260-
261- logger. lifecycle(" Downloading gitleaks ${ gitleaksVersion} (${ gitleaksTarSuffix} )..." )
262- new URL (tarUrl). withInputStream { is -> tarFile. bytes = is. bytes }
263-
264- // Extract just the gitleaks binary; Ant places it as out.parent/gitleaks == out
265- ant. untar(src : tarFile. absolutePath, dest : out. parent, compression : ' gzip' ) {
266- patternset { include(name : ' gitleaks' ) }
267- }
268243
269- if (! out. exists()) {
270- throw new GradleException (" gitleaks binary not found in ${ tarName} after extraction" )
244+ gitleaksTargets. each { suffix ->
245+ def out = gitleaksResourceDir. get(). file(" gitleaks/${ suffix} " ). asFile
246+ if (out. exists()) {
247+ logger. lifecycle(" gitleaks ${ gitleaksVersion} (${ suffix} ) already bundled, skipping." )
248+ return
249+ }
250+ out. parentFile. mkdirs()
251+
252+ def tarName = " gitleaks_${ gitleaksVersion} _${ suffix} .tar.gz"
253+ def tarUrl = " ${ baseDownloadUrl} /v${ gitleaksVersion} /${ tarName} "
254+ def tarFile = new File (tmpDir, tarName)
255+
256+ logger. lifecycle(" Downloading gitleaks ${ gitleaksVersion} (${ suffix} )..." )
257+ new URL (tarUrl). withInputStream { is -> tarFile. bytes = is. bytes }
258+
259+ // Extract to a suffix-specific temp dir to avoid name collision between targets
260+ def extractDir = new File (tmpDir, suffix)
261+ extractDir. mkdirs()
262+ ant. untar(src : tarFile. absolutePath, dest : extractDir. absolutePath, compression : ' gzip' ) {
263+ patternset { include(name : ' gitleaks' ) }
264+ }
265+ def extracted = new File (extractDir, ' gitleaks' )
266+ if (! extracted. exists()) {
267+ throw new GradleException (" gitleaks binary not found in ${ tarName} after extraction" )
268+ }
269+ extracted. renameTo(out)
270+ out. setExecutable(true )
271+ logger. lifecycle(" Bundled gitleaks (${ suffix} ) at ${ out} (${ out.length()} bytes)" )
271272 }
272- out. setExecutable(true )
273- logger. lifecycle(" Bundled gitleaks at ${ out} (${ out.length()} bytes)" )
274273 }
275274}
276275
277- // The JAR always ships with the bundled gitleaks binary (DEFAULT_VERSION).
278- // At runtime, GitleaksRunner uses it unless:
279- // - commit.secretScanning.scanner-path is set (explicit override), or
280- // - commit.secretScanning.version is set to a different version and auto-install is true
281- // (downloads and caches the requested version on first use).
282- // The download is a no-op when the binary is already present, so it only costs time once.
283276processResources. dependsOn(' downloadGitleaks' )
0 commit comments