Skip to content

Conversation

@N6REJ
Copy link
Contributor

@N6REJ N6REJ commented Nov 14, 2025

PR Type

Enhancement, Documentation


Description

  • Migrated build system from hybrid Ant/Gradle to pure Gradle with comprehensive task support

  • Implemented build.gradle.bruno-reference with 1183-line Gradle configuration including version resolution strategy (3-tier fallback: local releases.properties → remote modules-untouched → standard URL format)

  • Added core build tasks: release (interactive/non-interactive), releaseAll, clean, verify, validateProperties, checkModulesUntouched

  • Implemented helper functions for downloading/extracting binaries, finding 7-Zip executable, and generating hash files (MD5, SHA1, SHA256, SHA512)

  • Created comprehensive documentation suite covering release process, tasks reference, configuration guide, migration guide, and interactive mode

  • Added gradle.properties with performance optimizations (parallel execution, build caching, daemon configuration)

  • Integrated with modules-untouched repository for remote version management

  • Removed legacy build.xml Ant build file

  • Updated project README with building section and quick start commands


Diagram Walkthrough

flowchart LR
  A["Ant/Gradle Hybrid<br/>build.xml"] -->|"Migration"| B["Pure Gradle<br/>build.gradle"]
  B -->|"Version Resolution"| C["3-Tier Strategy:<br/>Local → Remote → URL"]
  B -->|"Core Tasks"| D["release, clean,<br/>verify, validateProperties"]
  B -->|"Helper Functions"| E["Binary Download,<br/>Hash Generation,<br/>7-Zip Detection"]
  B -->|"Integration"| F["modules-untouched<br/>Repository"]
  G["gradle.properties"] -->|"Performance"| B
  H["Comprehensive Docs"] -->|"Guides"| I["Release Process,<br/>Tasks, Configuration,<br/>Migration"]
Loading

File Walkthrough

Relevant files
Enhancement
1 files
build.gradle.bruno-reference
Pure Gradle build system for Bruno module with comprehensive task
support

build.gradle.bruno-reference

  • Added comprehensive 1183-line Gradle build configuration for Bruno
    module with version resolution strategy (3-tier fallback: local
    releases.properties → remote modules-untouched → standard URL format)
  • Implemented core build tasks: release (interactive/non-interactive),
    releaseAll, clean, verify, validateProperties, checkModulesUntouched
  • Added helper functions for downloading/extracting Bruno binaries,
    finding 7-Zip executable, generating hash files (MD5, SHA1, SHA256,
    SHA512), and managing available versions
  • Included information tasks: info, listVersions, listReleases with
    formatted output and build environment details
+1183/-0
Documentation
17 files
RELEASE-PROCESS.md
Complete release process documentation with step-by-step workflow
guide

.gradle-docs/RELEASE-PROCESS.md

  • Created 799-line comprehensive release process guide covering
    preparation, building, testing, publishing, and post-release tasks
  • Documented 13-step release workflow with detailed instructions for
    each phase including version checking, binary preparation, Git
    tagging, GitHub release creation, and releases.properties updates
  • Included version management strategies (semantic versioning for
    Memcached, date-based versioning for Bearsampp releases), release
    checklist, and troubleshooting section with common issues and
    solutions
  • Provided release timeline, prerequisites table, and best practices for
    version control, testing, documentation, and communication
+799/-0 
TASKS.md
Complete Gradle tasks reference with examples and documentation

.gradle-docs/TASKS.md

  • Created 594-line comprehensive Gradle tasks reference documenting all
    available tasks with syntax, parameters, examples, and expected output
  • Documented build tasks (release, clean), verification tasks (verify,
    validateProperties), and help tasks (info, listVersions, listReleases)
  • Included task dependencies, custom properties reference, and common
    task chains for typical workflows
  • Provided detailed output examples, error handling descriptions, and
    parameter tables for each task
+594/-0 
CONFIGURATION.md
Comprehensive configuration guide for build system setup 

.gradle-docs/CONFIGURATION.md

  • Created 746-line configuration guide covering all configuration files
    (build.properties, gradle.properties, releases.properties,
    settings.gradle)
  • Documented all build properties with descriptions, valid values, and
    impact analysis: bundle.name, bundle.release, bundle.type,
    bundle.format, build.path
  • Included Gradle properties for performance tuning, environment
    variables setup, advanced configuration options, and troubleshooting
    section with common configuration issues
  • Provided complete examples, validation procedures, and configuration
    checklist
+746/-0 
MIGRATION-GUIDE.md
Migration guide from Ant to pure Gradle build system         

.gradle-docs/MIGRATION-GUIDE.md

  • Created 603-line migration guide from Ant-based to pure Gradle build
    system with overview of changes and benefits
  • Documented command mapping for old Ant commands to new Gradle commands
    with side-by-side comparisons
  • Included migration steps for users, developers, and CI/CD pipelines
    with detailed instructions and verification procedures
  • Provided troubleshooting section, feature comparison table, best
    practices, and rollback instructions
+603/-0 
INTERACTIVE_MODE.md
Interactive mode documentation for release task                   

.gradle-docs/INTERACTIVE_MODE.md

  • Created 275-line documentation for interactive and non-interactive
    release build modes
  • Documented interactive mode flow with version selection by index or
    version string, input validation, and error handling
  • Included use cases for both modes, PowerShell-specific notes, and
    practical examples with expected output
  • Provided comparison with Bruno module implementation and related
    documentation references
+275/-0 
DESIGN-DECISIONS.md
Design decisions documentation for Gradle build system     

DESIGN-DECISIONS.md

  • Comprehensive documentation of design decisions for the Bearsampp
    Module Memcached build system
  • Explains rationale for using -PbundleVersion parameter instead of
    -Pversion
  • Documents directory display conventions with [bin] location indicators
  • Compares build system architecture choices (pure Gradle vs Ant/Gradle
    hybrid, Groovy vs Kotlin DSL)
  • Details property naming conventions, directory structure, and release
    naming format
+371/-0 
BUILD-SYSTEM.md
Build system specification and Gradle configuration guide

BUILD-SYSTEM.md

  • Detailed specification of the pure Gradle build system using Groovy
    DSL
  • Documents system requirements (Java 8+, Gradle 7.0+, 7-Zip)
  • Provides Groovy DSL syntax examples and best practices
  • Includes installation instructions and verification procedures
  • Contains troubleshooting guide for common issues
+418/-0 
MIGRATION-SUMMARY.md
Migration summary from Ant to pure Gradle build                   

MIGRATION-SUMMARY.md

  • Summarizes migration from hybrid Ant/Gradle to pure Gradle build
    system
  • Documents removed Ant build files and updated configuration files
  • Lists new features including pure Gradle implementation, build
    verification, and version management
  • Provides command changes comparison between old Ant and new Gradle
    commands
  • Includes testing results and verification procedures
+298/-0 
INDEX.md
Documentation index and navigation guide                                 

.gradle-docs/INDEX.md

  • Complete documentation index for the Bearsampp Module Memcached build
    system
  • Provides navigation structure for all documentation files
  • Organizes documentation by topic (Build System, Tasks, Configuration,
    Release Process)
  • Includes quick reference section and documentation standards
  • Contains maintenance guidelines and contribution instructions
+313/-0 
FINAL-SUMMARY.md
Final summary of build system migration completion             

FINAL-SUMMARY.md

  • Summarizes completed migration to match Bruno and Apache module
    patterns
  • Documents key changes including clean terminal output with ASCII
    characters
  • Explains interactive release mode with numbered selection
  • Shows consistency with other Bearsampp modules
  • Provides usage examples and testing results
+295/-0 
CONFIGURATION_SUMMARY.md
Configuration guide for build properties and Gradle settings

.gradle-docs/CONFIGURATION_SUMMARY.md

  • Documents build.properties configuration file and all available
    properties
  • Explains Gradle project settings and path configuration
  • Details directory structure for source and build output
  • Includes environment variables and task configuration
  • Provides customization examples and troubleshooting guide
+271/-0 
MIGRATION_SUMMARY.md
Migration guide from Ant to modern Gradle build system     

.gradle-docs/MIGRATION_SUMMARY.md

  • Details migration from hybrid Ant/Gradle to modern pure Gradle build
  • Documents build script improvements and new helper functions
  • Lists task changes and new features (hash generation,
    modules-untouched integration)
  • Explains removed dependencies and backward compatibility
    considerations
  • Includes testing results and recommended next steps
+248/-0 
MODULES_UNTOUCHED_INTEGRATION.md
Modules-untouched repository integration documentation     

.gradle-docs/MODULES_UNTOUCHED_INTEGRATION.md

  • Explains integration with Bearsampp modules-untouched repository for
    version management
  • Documents remote properties file fetching and version resolution
    strategy
  • Describes available commands (checkModulesUntouched, listReleases)
  • Details error handling and graceful degradation for network failures
  • Includes maintenance procedures for adding new versions and updating
    URLs
+221/-0 
FEATURE_SUMMARY.md
Build system features and capabilities overview                   

.gradle-docs/FEATURE_SUMMARY.md

  • Comprehensive overview of build system features and capabilities
  • Highlights native Gradle build, automatic hash generation, and
    modules-untouched integration
  • Documents flexible version management and archive format support
  • Lists comprehensive verification and task set
  • Describes benefits for developers, release management, and QA
+111/-0 
README.md
Main Gradle build documentation and quick start guide       

.gradle-docs/README.md

  • Main documentation entry point for Gradle build system
  • Provides quick start guide with essential commands
  • Summarizes key features and build system architecture
  • Lists requirements and documentation file references
  • Directs users to support resources
+81/-0   
README.md
Add building section with Gradle build instructions           

README.md

  • Adds comprehensive "Building" section to project README
  • Documents prerequisites (Java 8+, Gradle, 7-Zip)
  • Provides quick start commands for common build tasks
  • Lists available tasks organized by category (Build, Help,
    Verification)
  • Explains build configuration and output structure
+79/-0   
QUICK_REFERENCE.md
Quick reference guide for common build commands                   

.gradle-docs/QUICK_REFERENCE.md

  • Quick reference guide for common Gradle commands
  • Organizes commands by category (Build, Information, Verification)
  • Provides build output location and configuration details
  • Includes troubleshooting section for common issues
  • Contains helpful tips for Gradle usage
+99/-0   
Configuration changes
1 files
gradle.properties
Gradle properties for performance optimization                     

gradle.properties

  • Gradle daemon configuration for faster builds
  • Performance optimizations (parallel execution, build caching,
    configuration on demand)
  • JVM arguments for Gradle daemon memory management
  • Console output and warning mode settings
  • File system watching configuration for Gradle 7.0+
+30/-0   
Additional files
13 files
bearsampp.conf [link]   
bearsampp.conf [link]   
bearsampp.conf [link]   
bearsampp.conf [link]   
bearsampp.conf [link]   
bearsampp.conf [link]   
bearsampp.conf [link]   
bearsampp.conf [link]   
bearsampp.conf [link]   
bearsampp.conf [link]   
bearsampp.conf [link]   
bearsampp.conf [link]   
build.xml +0/-38   

@N6REJ N6REJ added the enhancement ✨ Improve program label Nov 14, 2025
@qodo-merge-pro
Copy link
Contributor

qodo-merge-pro bot commented Nov 14, 2025

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
🔴
Weak hash algorithms

Description: The build generates and publishes MD5 and SHA1 hashes alongside stronger SHA256/512, which
encourages use of weak, collision-prone algorithms that are considered insecure for
integrity; avoid publishing MD5/SHA1.
build.gradle.bruno-reference [722-745]

Referred Code
// Generate MD5
def md5File = new File("${file.absolutePath}.md5")
def md5Hash = calculateHash(file, 'MD5')
md5File.text = "${md5Hash} ${file.name}\n"
println "  Created: ${md5File.name}"

// Generate SHA1
def sha1File = new File("${file.absolutePath}.sha1")
def sha1Hash = calculateHash(file, 'SHA-1')
sha1File.text = "${sha1Hash} ${file.name}\n"
println "  Created: ${sha1File.name}"

// Generate SHA256
def sha256File = new File("${file.absolutePath}.sha256")
def sha256Hash = calculateHash(file, 'SHA-256')
sha256File.text = "${sha256Hash} ${file.name}\n"
println "  Created: ${sha256File.name}"

// Generate SHA512
def sha512File = new File("${file.absolutePath}.sha512")
def sha512Hash = calculateHash(file, 'SHA-512')


 ... (clipped 3 lines)
Executable trust risk

Description: The 7-Zip executable is resolved from environment and hardcoded Windows paths then
executed with user-controlled inputs, which on non-Windows or PATH-hijack scenarios could
execute an unintended binary leading to command execution; restrict execution to trusted
paths or verify the binary signature.
build.gradle.bruno-reference [673-714]

Referred Code
// Helper function to find 7-Zip executable
def find7ZipExecutable() {
    // Check environment variable
    def sevenZipHome = System.getenv('7Z_HOME')
    if (sevenZipHome) {
        def exe = file("${sevenZipHome}/7z.exe")
        if (exe.exists()) {
            return exe.absolutePath
        }
    }

    // Check common installation paths
    def commonPaths = [
        'C:/Program Files/7-Zip/7z.exe',
        'C:/Program Files (x86)/7-Zip/7z.exe',
        'D:/Program Files/7-Zip/7z.exe',
        'D:/Program Files (x86)/7-Zip/7z.exe'
    ]

    for (path in commonPaths) {
        def exe = file(path)


 ... (clipped 21 lines)
Supply chain trust

Description: The build fetches bruno.properties over HTTPS but does not verify authenticity beyond TLS
and accepts any content to decide download sources, enabling supply-chain poisoning if the
remote is compromised; add pinning, signature verification, or checksum validation.
build.gradle.bruno-reference [105-128]

Referred Code
// This is the primary source for version information when not in releases.properties
def fetchModulesUntouchedProperties() {
    def propsUrl = "https://raw.githubusercontent.com/Bearsampp/modules-untouched/main/modules/bruno.properties"

    println "Fetching bruno.properties from modules-untouched repository..."
    println "  URL: ${propsUrl}"

    def tempFile = file("${bundleTmpDownloadPath}/bruno-untouched.properties")
    tempFile.parentFile.mkdirs()

    try {
        ant.get(src: propsUrl, dest: tempFile, verbose: false, ignoreerrors: false)

        def props = new Properties()
        tempFile.withInputStream { props.load(it) }

        println "  ✓ Successfully loaded ${props.size()} versions from modules-untouched"
        return props
    } catch (Exception e) {
        println "  ✗ Warning: Could not fetch bruno.properties from modules-untouched: ${e.message}"
        println "  Will fall back to standard URL format if needed"


 ... (clipped 3 lines)
Unsigned binary download

Description: When a version is missing locally, the code constructs a release URL and downloads
binaries from GitHub without validating signatures or expected checksums before
extraction, risking malicious binaries; enforce checksum/signature verification prior to
use.
build.gradle.bruno-reference [130-189]

Referred Code
// Function to download from modules-untouched repository
def downloadFromModulesUntouched(String version, File destDir) {
    println "Version ${version} not found in releases.properties"
    println "Checking modules-untouched repository..."

    // First, try to fetch bruno.properties from modules-untouched
    def untouchedProps = fetchModulesUntouchedProperties()
    def untouchedUrl = null

    if (untouchedProps) {
        untouchedUrl = untouchedProps.getProperty(version)
        if (untouchedUrl) {
            println "Found version ${version} in modules-untouched bruno.properties"
            println "Downloading from:"
            println "  ${untouchedUrl}"
        } else {
            println "Version ${version} not found in modules-untouched bruno.properties"
            println "Attempting to construct URL based on standard format..."
            // Fallback to constructed URL
            untouchedUrl = "https://github.com/Bearsampp/modules-untouched/releases/download/bruno-${version}/bruno-${version}-win64.7z"
            println "  ${untouchedUrl}"


 ... (clipped 39 lines)
External process execution

Description: The script executes an external 7-Zip process with paths derived from downloaded archives;
while arguments are passed as an array (mitigating injection), lack of validation of paths
and reliance on system PATH may allow unintended binary execution; ensure path
canonicalization and trusted executable location.
build.gradle.bruno-reference [246-273]

Referred Code
// Use 7zip or built-in extraction
if (filename.endsWith('.7z')) {
    // Try to use 7zip if available
    def sevenZipPath = find7ZipExecutable()
    if (sevenZipPath) {
        def command = [
            sevenZipPath.toString(),
            'x',
            downloadedFile.absolutePath.toString(),
            "-o${extractPath.absolutePath}".toString(),
            '-y'
        ]
        def process = new ProcessBuilder(command as String[])
            .directory(extractPath)
            .redirectErrorStream(true)
            .start()

        process.inputStream.eachLine { line ->
            if (line.trim()) println "    ${line}"
        }



 ... (clipped 7 lines)
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status:
Missing audit logs: The new build tasks perform critical actions (downloading, extracting, packaging) without
structured audit logging that captures user ID and standardized timestamps, relying mainly
on println outputs.

Referred Code
    println "  ${downloadUrl}"

    // Determine filename from URL
    def filename = downloadUrl.substring(downloadUrl.lastIndexOf('/') + 1)
    def downloadDir = file(bundleTmpDownloadPath)
    downloadDir.mkdirs()

    downloadedFile = file("${downloadDir}/${filename}")

    // Download if not already present
    if (!downloadedFile.exists()) {
        println "  Downloading to: ${downloadedFile}"
        ant.get(src: downloadUrl, dest: downloadedFile, verbose: true)
        println "  Download complete"
    } else {
        println "  Using cached file: ${downloadedFile}"
    }
}

// Extract the archive
def extractDir = file(bundleTmpExtractPath)


 ... (clipped 65 lines)

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status:
Limited error context: Several GradleExceptions provide generic messages without including actionable context
like target URLs or paths in all cases, and some downloads use ant.get without try/catch,
risking unhandled failures.

Referred Code
    // Download if not already present
    if (!downloadedFile.exists()) {
        println "  Downloading to: ${downloadedFile}"
        ant.get(src: downloadUrl, dest: downloadedFile, verbose: true)
        println "  Download complete"
    } else {
        println "  Using cached file: ${downloadedFile}"
    }
}

// Extract the archive
def extractDir = file(bundleTmpExtractPath)
extractDir.mkdirs()
println "  Extracting archive..."
def extractPath = file("${extractDir}/${version}")
if (extractPath.exists()) {
    delete extractPath
}
extractPath.mkdirs()

// Determine filename from downloaded file


 ... (clipped 38 lines)

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status:
Verbose errors: Error and warning outputs echo full filesystem paths and external URLs directly to
console, which may be user-facing in some contexts and could disclose internal details.

Referred Code
            // Download and extract to bearsampp-build/tmp
            bundleSrcFinal = downloadAndExtractBruno(bundleVersion, file(bundleTmpExtractPath))
        } catch (Exception e) {
            throw new GradleException("""
                Failed to download Bruno binaries: ${e.message}

                You can manually download and extract Bruno binaries to:
                  ${bundleSrcDest}/

                Or check that version ${bundleVersion} exists in releases.properties
            """.stripIndent())
        }
    }
}

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status:
Unsanitized inputs: External inputs like constructed URLs and version strings from properties/user input are
used for downloads and command execution without explicit validation against allowed
patterns or schemes.

Referred Code
def versionProperty = project.findProperty('bundleVersion')

doLast {
    def versionToBuild = versionProperty

    if (!versionToBuild) {
        // Interactive mode - prompt for version
        def availableVersions = getAvailableVersions()

        if (availableVersions.isEmpty()) {
            throw new GradleException("No versions found in bin/ directory")
        }

        println ""
        println "=".multiply(70)
        println "Interactive Release Mode"
        println "=".multiply(70)
        println ""
        println "Available versions:"

        // Show versions with location tags


 ... (clipped 51 lines)

Learn more about managing compliance generic rules or creating your own custom rules

  • Update
Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@qodo-merge-pro
Copy link
Contributor

qodo-merge-pro bot commented Nov 14, 2025

PR Code Suggestions ✨

Latest suggestions up to 520124b

CategorySuggestion                                                                                                                                    Impact
Possible issue
Quote paths in 7-Zip command

Quote the file and output paths in the 7-Zip extraction command to correctly
handle spaces and special characters.

build.gradle.bruno-reference [249-273]

 def sevenZipPath = find7ZipExecutable()
 if (sevenZipPath) {
+    // Quote paths to handle spaces/special characters safely
     def command = [
         sevenZipPath.toString(),
         'x',
-        downloadedFile.absolutePath.toString(),
-        "-o${extractPath.absolutePath}".toString(),
+        "\"${downloadedFile.absolutePath}\"",
+        "\"-o${extractPath.absolutePath}\"",
         '-y'
     ]
     def process = new ProcessBuilder(command as String[])
         .directory(extractPath)
         .redirectErrorStream(true)
         .start()
 
     process.inputStream.eachLine { line ->
         if (line.trim()) println "    ${line}"
     }
 
     def exitCode = process.waitFor()
     if (exitCode != 0) {
         throw new GradleException("7zip extraction failed with exit code: ${exitCode}")
     }
 } else {
     throw new GradleException("7zip not found. Please install 7zip or extract manually.")
 }
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why: This is a valid robustness improvement that prevents the build from failing if file paths contain spaces, which is a common scenario on Windows environments.

Low
Quote 7-Zip add command paths

Quote the archive and input paths in the 7-Zip compression command to correctly
handle spaces and special characters.

build.gradle.bruno-reference [596-644]

 // Build archive filename
 def destFile = file("${buildBinsPath}/bearsampp-${bundleName}-${bundleVersion}-${bundleRelease}")
 
-// Compress based on format
 if (bundleFormat == '7z') {
-    // 7z format
     def archiveFile = file("${destFile}.7z")
     if (archiveFile.exists()) {
         delete archiveFile
     }
 
     println "Compressing ${bundleName}${bundleVersion} to ${archiveFile.name}..."
 
-    // Find 7z executable
     def sevenZipExe = find7ZipExecutable()
     if (!sevenZipExe) {
         throw new GradleException("7-Zip not found. Please install 7-Zip or set 7Z_HOME environment variable.")
     }
 
     println "Using 7-Zip: ${sevenZipExe}"
 
-    // Create 7z archive
+    // Quote archive path and input path to handle spaces
     def command = [
-        sevenZipExe,
+        sevenZipExe.toString(),
         'a',
         '-t7z',
-        archiveFile.absolutePath.toString(),
-        '.'
+        "\"${archiveFile.absolutePath}\"",
+        "\".\""
     ]
 
     def process = new ProcessBuilder(command as String[])
         .directory(brunoPrepPath)
         .redirectErrorStream(true)
         .start()
 
     process.inputStream.eachLine { line ->
         if (line.trim()) println "  ${line}"
     }
 
     def exitCode = process.waitFor()
     if (exitCode != 0) {
         throw new GradleException("7zip compression failed with exit code: ${exitCode}")
     }
 
     println "Archive created: ${archiveFile}"
 
-    // Generate hash files
     println "Generating hash files..."
     generateHashFiles(archiveFile)
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why: This is a valid robustness improvement that prevents the build from failing if file paths contain spaces, which is a common scenario on Windows environments.

Low
General
Add network timeout to downloads

Add a maxtime attribute to the ant.get call to set a network timeout, preventing
the build from hanging indefinitely on network issues.

build.gradle.bruno-reference [106-128]

-// Function to fetch bruno.properties from modules-untouched repository
-// This is the primary source for version information when not in releases.properties
 def fetchModulesUntouchedProperties() {
     def propsUrl = "https://raw.githubusercontent.com/Bearsampp/modules-untouched/main/modules/bruno.properties"
     
     println "Fetching bruno.properties from modules-untouched repository..."
     println "  URL: ${propsUrl}"
     
     def tempFile = file("${bundleTmpDownloadPath}/bruno-untouched.properties")
     tempFile.parentFile.mkdirs()
     
     try {
-        ant.get(src: propsUrl, dest: tempFile, verbose: false, ignoreerrors: false)
+        // Set a max time (in seconds) to avoid indefinite hangs on bad networks
+        ant.get(src: propsUrl, dest: tempFile, verbose: false, ignoreerrors: false, maxtime: 60)
         
         def props = new Properties()
         tempFile.withInputStream { props.load(it) }
         
         println "  ✓ Successfully loaded ${props.size()} versions from modules-untouched"
         return props
     } catch (Exception e) {
         println "  ✗ Warning: Could not fetch bruno.properties from modules-untouched: ${e.message}"
         println "  Will fall back to standard URL format if needed"
         return null
     }
 }

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 5

__

Why: This suggestion improves the build's resilience to network issues by adding a timeout to the ant.get call, preventing indefinite hangs and making the build more robust.

Low
  • More

Previous suggestions

Suggestions up to commit ac3bd8a
CategorySuggestion                                                                                                                                    Impact
Possible issue
Enable fallback when local file missing

Modify the downloadAndExtractBruno function to handle a missing
releases.properties file gracefully, allowing the build to proceed with remote
fallback logic instead of throwing an exception.

build.gradle.bruno-reference [194-197]

+def releases = new Properties()
+def downloadUrl = null
+
 def releasesFile = file('releases.properties')
-if (!releasesFile.exists()) {
-    throw new GradleException("releases.properties not found")
+if (releasesFile.exists()) {
+    releasesFile.withInputStream { releases.load(it) }
+    downloadUrl = releases.getProperty(version)
+} else {
+    println "releases.properties not found, will attempt remote fallback (modules-untouched) and constructed URL."
 }
Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a critical bug where the build fails if releases.properties is missing, which prevents the documented 3-tier fallback mechanism from working.

High
General
Add ZIP fallback if 7-Zip missing

In the release task, add a fallback to create a ZIP archive if the bundle.format
is 7z but the 7-Zip executable cannot be found, preventing a hard build failure.

build.gradle.bruno-reference [610-613]

 def sevenZipExe = find7ZipExecutable()
 if (!sevenZipExe) {
-    throw new GradleException("7-Zip not found. Please install 7-Zip or set 7Z_HOME environment variable.")
+    println "[WARNING] 7-Zip not found. Falling back to ZIP format for this build."
+    // Fallback to ZIP archive creation
+    def zipArchive = file("${destFile}.zip")
+    if (zipArchive.exists()) {
+        delete zipArchive
+    }
+    ant.zip(destfile: zipArchive, basedir: brunoPrepPath)
+    println "Archive created (fallback ZIP): ${zipArchive}"
+    println "Generating hash files..."
+    generateHashFiles(zipArchive)
+    return
 }
Suggestion importance[1-10]: 7

__

Why: This is a valuable suggestion that improves the build script's resilience by adding a fallback to ZIP compression if 7-Zip is not found, which is especially useful for non-Windows or CI environments.

Medium
Suggestions up to commit f9abd59
CategorySuggestion                                                                                                                                    Impact
High-level
Extract build logic into a reusable Gradle plugin

Extract the common build logic from the large, monolithic build script into a
reusable custom Gradle plugin. This will reduce code duplication and improve
maintainability across modules.

Examples:

build.gradle.bruno-reference [1-1183]
/*
 * Bearsampp Module Bruno - Gradle Build
 *
 * This is a 100% Gradle build configuration for the Bruno module.
 * It handles downloading, extracting, and packaging Bruno releases.
 *
 * VERSION RESOLUTION STRATEGY (3-tier fallback):
 *   1. Local releases.properties (primary source)
 *   2. Remote modules-untouched bruno.properties (automatic fallback)
 *      URL: https://github.com/Bearsampp/modules-untouched/blob/main/modules/bruno.properties

 ... (clipped 1173 lines)

Solution Walkthrough:

Before:

// In module-bruno/build.gradle.bruno-reference
plugins { id 'base' }

// ~100 lines of path and property configuration for bruno
ext {
  bundleName = 'bruno'
  // ... many other paths
}

// ~100 lines of helper functions for downloading/extracting bruno
def downloadAndExtractBruno(String version, File destDir) {
  // ... complex logic to download from github, fallback, etc.
}

// ~200 lines for the 'release' task for bruno
tasks.register('release') {
  doLast {
    // ... complex logic to find binaries, copy files, run 7zip
  }
}

// ~500 more lines for other tasks (info, verify, clean, etc.) for bruno
// ... and the same ~1000 lines are copied and adapted in other modules

After:

// In buildSrc/src/main/groovy/com.bearsampp.module-build.gradle
class ModuleBuildPlugin implements Plugin<Project> {
    void apply(Project project) {
        // ~1000 lines of generic build logic extracted here
        // - Generic download/extract functions
        // - Generic release task
        // - Generic info, verify tasks

        project.tasks.register('release', ReleaseTask) {
            // Task is configured by an extension
        }
    }
}

// In module-bruno/build.gradle
plugins {
    id 'com.bearsampp.module-build'
}

// Configure the plugin for this specific module
moduleBuild {
    bundleName = 'bruno'
    bundleType = 'tools'
    executableName = 'bruno.exe'
}
Suggestion importance[1-10]: 9

__

Why: This is a critical architectural suggestion that correctly identifies the major flaw of code duplication in the monolithic build script and proposes a scalable, maintainable solution using a custom Gradle plugin.

High
Security
Prevent path traversal security vulnerability

Add input validation for the bundleVersion project property to prevent a path
traversal security vulnerability. The validation should ensure the version
string contains only safe characters.

build.gradle.bruno-reference [422-427]

 doLast {
     def versionToBuild = versionProperty
+
+    if (versionToBuild && (versionToBuild.contains('..') || !versionToBuild.matches("^[\\w.-]+\$"))) {
+        throw new GradleException("Invalid characters in bundleVersion. Only alphanumeric characters, dots, and hyphens are allowed.")
+    }
 
     if (!versionToBuild) {
         // Interactive mode - prompt for version
         def availableVersions = getAvailableVersions()
Suggestion importance[1-10]: 9

__

Why: This is a valid and critical security suggestion that addresses a potential path traversal vulnerability when a malicious bundleVersion is passed, which could lead to file system manipulation outside the intended directories.

High
General
Improve executable discovery from PATH

Improve the reliability of finding the 7z.exe executable from the system PATH.
Instead of using the first result from the where command, iterate through all
returned paths and use the first one that executes successfully.

build.gradle.bruno-reference [701-708]

 def process = ['where', '7z.exe'].execute()
 process.waitFor()
 if (process.exitValue() == 0) {
     def output = process.text.trim()
     if (output) {
-        return output.split('\n')[0].trim()
+        // Find the first valid 7z.exe that can execute
+        for (String path in output.split('\n')) {
+            def candidate = path.trim()
+            try {
+                def testProcess = [candidate, '--help'].execute()
+                testProcess.waitFor()
+                if (testProcess.exitValue() == 0) {
+                    return candidate
+                }
+            } catch (Exception ignored) {
+                // Ignore if this path is not a valid executable
+            }
+        }
     }
 }
Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies a potential issue where the first 7z.exe in the PATH might be non-functional and proposes a robust solution to iterate and test all found executables, improving the build script's reliability.

Low
Fix corrupted characters in documentation

Fix corrupted Unicode characters in an example output in MIGRATION-SUMMARY.md by
replacing them with ASCII characters, ensuring consistency with the project's
design decisions.

MIGRATION-SUMMARY.md [239-241]

-╔════════════════════════════════════════════════════════════════════════════╗
-║                    Build Environment Verification                          ║
-╚═══════════════════════════════════════════════════════════════════════════╝
+============================================================================
+|                    Build Environment Verification                          |
+============================================================================
Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies corrupted Unicode characters in the documentation and proposes a fix that aligns with the project's stated goal of using ASCII for compatibility.

Low
Update documentation for consistency

Update an example output in .gradle-docs/README.md to use ASCII characters
instead of Unicode, ensuring it accurately reflects the build system's output
and maintains documentation consistency.

.gradle-docs/README.md [216-219]

 Expected output:
 

-╔════════════════════════════════════════════════════════════════════════════╗
-║ Build Environment Verification ║
-╚════════════════════════════════════════════════════════════════════════════╝
+============================================================================
+| Build Environment Verification |
+============================================================================







<details><summary>Suggestion importance[1-10]: 6</summary>

__

Why: The suggestion correctly points out an inconsistency in the documentation, as the example output uses Unicode characters while the project standard is ASCII, improving documentation accuracy.


</details></details></td><td align=center>Low

</td></tr><tr><td>



<details><summary>Use relative path in documentation<!-- not_implemented --></summary>

___

**Replace the hardcoded absolute path in the <code>README.md</code> example with a generic, <br>relative path to make the documentation universally applicable.**

[README.md [120-121]](https://github.com/Bearsampp/module-memcached/pull/23/files#diff-b335630551682c19a781afebcf4d07bf978fb1f8ac04c6bf87428ed5106870f5R120-R121)

```diff
 # 4. Test package
-7z t C:\Users\troy\Bearsampp-build\release\bearsampp-memcached-1.6.39-2025.8.20.7z
+# The release package is created in the 'build/release' directory
+7z t build/release/bearsampp-memcached-1.6.39-2025.8.20.7z
Suggestion importance[1-10]: 4

__

Why: The suggestion correctly identifies a hardcoded, user-specific path in the documentation, and replacing it with a generic path improves its usability for other developers.

Low

@qodo-merge-pro
Copy link
Contributor

qodo-merge-pro bot commented Nov 15, 2025

PR Reviewer Guide 🔍

(Review updated until commit 520124b)

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 4 🔵🔵🔵🔵⚪
🧪 No relevant tests
🔒 Security concerns

Integrity verification:
Downloads from remote URLs are used without validating checksums or signatures, which risks supply-chain tampering. Add hash/PGP verification against a trusted source before extraction or packaging. Also ensure temporary download paths have safe permissions and avoid executing untrusted content during extraction.

⚡ Recommended focus areas for review

Portability Risk

7-Zip discovery and invocation assume Windows-specific paths and 'where' command, and only 7z.exe is supported; this may break on non-Windows or environments without 7-Zip in PATH. Consider supporting zip as fallback automatically or improving cross-platform detection.

// Helper function to find 7-Zip executable
def find7ZipExecutable() {
    // Check environment variable
    def sevenZipHome = System.getenv('7Z_HOME')
    if (sevenZipHome) {
        def exe = file("${sevenZipHome}/7z.exe")
        if (exe.exists()) {
            return exe.absolutePath
        }
    }

    // Check common installation paths
    def commonPaths = [
        'C:/Program Files/7-Zip/7z.exe',
        'C:/Program Files (x86)/7-Zip/7z.exe',
        'D:/Program Files/7-Zip/7z.exe',
        'D:/Program Files (x86)/7-Zip/7z.exe'
    ]

    for (path in commonPaths) {
        def exe = file(path)
        if (exe.exists()) {
            return exe.absolutePath
        }
    }

    // Try to find in PATH
    try {
        def process = ['where', '7z.exe'].execute()
        process.waitFor()
        if (process.exitValue() == 0) {
            def output = process.text.trim()
            if (output) {
                return output.split('\n')[0].trim()
            }
        }
    } catch (Exception e) {
        // Ignore
    }

    return null
}
Network Robustness

Remote fetch/download lacks checksum verification and retry/backoff; failures could go unnoticed or produce corrupted artifacts. Add checksum validation (if available), retries, and timeouts for 'ant.get' operations.

// Function to download from modules-untouched repository
def downloadFromModulesUntouched(String version, File destDir) {
    println "Version ${version} not found in releases.properties"
    println "Checking modules-untouched repository..."

    // First, try to fetch bruno.properties from modules-untouched
    def untouchedProps = fetchModulesUntouchedProperties()
    def untouchedUrl = null

    if (untouchedProps) {
        untouchedUrl = untouchedProps.getProperty(version)
        if (untouchedUrl) {
            println "Found version ${version} in modules-untouched bruno.properties"
            println "Downloading from:"
            println "  ${untouchedUrl}"
        } else {
            println "Version ${version} not found in modules-untouched bruno.properties"
            println "Attempting to construct URL based on standard format..."
            // Fallback to constructed URL
            untouchedUrl = "https://github.com/Bearsampp/modules-untouched/releases/download/bruno-${version}/bruno-${version}-win64.7z"
            println "  ${untouchedUrl}"
        }
    } else {
        println "Could not fetch bruno.properties, using standard URL format..."
        // Fallback to constructed URL
        untouchedUrl = "https://github.com/Bearsampp/modules-untouched/releases/download/bruno-${version}/bruno-${version}-win64.7z"
        println "  ${untouchedUrl}"
    }

    // Determine filename from URL
    def filename = untouchedUrl.substring(untouchedUrl.lastIndexOf('/') + 1)
    def downloadDir = file(bundleTmpDownloadPath)
    downloadDir.mkdirs()

    def downloadedFile = file("${downloadDir}/${filename}")

    // Download if not already present
    if (!downloadedFile.exists()) {
        println "  Downloading to: ${downloadedFile}"
        try {
            ant.get(src: untouchedUrl, dest: downloadedFile, verbose: true)
            println "  Download complete from modules-untouched"
        } catch (Exception e) {
            throw new GradleException("""
                Failed to download from modules-untouched: ${e.message}

                Tried URL: ${untouchedUrl}

                Please verify:
                1. Version ${version} exists in modules-untouched repository
                2. The URL is correct in bruno.properties or matches format: bruno-{version}/bruno-{version}-win64.7z
                3. You have internet connectivity
            """.stripIndent())
        }
    } else {
        println "  Using cached file: ${downloadedFile}"
    }

    return downloadedFile
}
Version Parsing

Custom version sorting/comparison converts tokens to integers; non-numeric or pre-release identifiers will throw exceptions. Consider a safer comparator that tolerates non-numeric segments or uses semantic version parsing.

def sortedVersions = untouchedProps.sort { a, b ->
    // Simple version comparison
    def aParts = a.key.tokenize('.')
    def bParts = b.key.tokenize('.')
    for (int i = 0; i < Math.min(aParts.size(), bParts.size()); i++) {
        def aNum = aParts[i].toInteger()
        def bNum = bParts[i].toInteger()
        if (aNum != bNum) return aNum <=> bNum
    }
    return aParts.size() <=> bParts.size()
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants