From ae74811b59d81291f9b1d4cb6feea342e5b1375d Mon Sep 17 00:00:00 2001 From: Bear Date: Tue, 16 Dec 2025 00:09:43 -0600 Subject: [PATCH 1/2] does modules-untouched download and upload automatcially so nothing is needed to be done except run gradle release --- .gradle-docs/README.md | 114 ++++++++++++- README.md | 2 + build.gradle | 375 +++++++++++++++++++++++++++++++++++++++-- 3 files changed, 473 insertions(+), 18 deletions(-) diff --git a/.gradle-docs/README.md b/.gradle-docs/README.md index cd2bfaa..15f8d97 100644 --- a/.gradle-docs/README.md +++ b/.gradle-docs/README.md @@ -18,6 +18,7 @@ The Bearsampp Module Memcached project has been converted to a **pure Gradle build system**, replacing the legacy Ant build configuration. This provides: - **Modern Build System** - Native Gradle tasks and conventions +- **Automated Workflow** - Automatic upstream release creation - **Better Performance** - Incremental builds and caching - **Simplified Maintenance** - Pure Groovy/Gradle DSL - **Enhanced Tooling** - IDE integration and dependency management @@ -291,6 +292,106 @@ bearsampp-build/bins/memcached/2025.8.20/ └── bearsampp-memcached-1.6.29-2025.8.20.7z.sha512 ``` +### Automated Upstream Workflow + +The build system includes an **intelligent automated workflow** that handles upstream releases: + +#### Smart Version Detection + +When you run `gradle release -PbundleVersion=1.6.39`, the build automatically: + +1. **Checks modules-untouched** - Does version 1.6.39 exist? + - ✅ **YES** → Skip to step 6 (normal build process) + - ❌ **NO** → Continue to step 2 (create upstream release) + +2. **Downloads from nono303** - Clones nono303/memcached repository and extracts `libevent-2.1/x64` folder + +3. **Extracts version** - Uses PowerShell to get version from `memcached.exe` file properties + +4. **Creates 7z archive** - Creates `memcached-{version}-win64.7z` with all binaries + +5. **Creates GitHub release**: + - Tag: `memcached-{bundle.release}` (e.g., `memcached-2025.8.20`) + - Title: `Memcache {VERSION}` (e.g., `Memcache 1.6.39`) + - Description: `Memcached {VERSION} automated build` + - Asset: The 7z file + - Uses GitHub CLI (`gh`) with `GH_PAT` environment variable + +6. **Waits for update** - Polls modules-untouched every 10 seconds (max 5 minutes) until version appears + +7. **Normal build process**: + - Downloads binaries from modules-untouched + - Overlays configuration from `bin/memcached{version}/` + - Packages final release + - Generates hash files + +#### Prerequisites for Upstream Workflow + +| Requirement | Purpose | +|-------------------|--------------------------------------------------| +| **GitHub CLI** | Creating releases (`gh release create`) | +| **GH_PAT** | GitHub Personal Access Token (environment var) | +| **7-Zip** | Creating 7z archives | + +Set up GitHub authentication: +```bash +# Set environment variable +$env:GH_PAT = 'your_github_personal_access_token' + +# Or authenticate with gh CLI +gh auth login +``` + +#### Example Workflow + +```bash +# Build a new version that doesn't exist in modules-untouched yet +gradle release -PbundleVersion=1.6.40 + +# Output: +# ====================================================================== +# Building memcached 1.6.40 +# ====================================================================== +# +# Checking modules-untouched for version 1.6.40... +# ⚠ Version 1.6.40 not found in modules-untouched +# Starting upstream release process... +# +# ====================================================================== +# Downloading Memcached 1.6.40 from nono303/memcached +# ====================================================================== +# Repository: https://github.com/nono303/memcached +# Branch: master +# Subfolder: libevent-2.1/x64 +# ... +# Successfully copied 15 files +# +# ====================================================================== +# Creating Upstream Release +# ====================================================================== +# Memcached version: 1.6.40 +# Creating archive: memcached-1.6.40-win64.7z +# ... +# Creating GitHub release: +# Tag: memcached-2025.8.20 +# Title: Memcache 1.6.40 +# Asset: memcached-1.6.40-win64.7z +# GitHub release created successfully! +# +# ====================================================================== +# Waiting for modules-untouched to be updated... +# ====================================================================== +# [1] Checking modules-untouched (0s elapsed)... +# Not yet available, waiting 10s... +# [2] Checking modules-untouched (10s elapsed)... +# ✓ Version 1.6.40 found in modules-untouched! +# +# ====================================================================== +# Continuing with normal release process +# ====================================================================== +# ... +``` + ### Version Management The build system uses a **two-source approach** for building releases: @@ -302,12 +403,13 @@ The build system uses a **two-source approach** for building releases: When you run `gradle release -PbundleVersion=1.6.39`, the build: -1. **Downloads** Memcached binaries from modules-untouched (cached for reuse) -2. **Extracts** the binaries to a temporary location -3. **Locates** configuration files in `bin/memcached1.6.39/` or `bin/archived/memcached1.6.39/` -4. **Combines** binaries + configuration files -5. **Packages** the release archive -6. **Generates** hash files (MD5, SHA1, SHA256, SHA512) +1. **Checks** if version exists in modules-untouched (if not, creates upstream release automatically) +2. **Downloads** Memcached binaries from modules-untouched (cached for reuse) +3. **Extracts** the binaries to a temporary location +4. **Locates** configuration files in `bin/memcached1.6.39/` or `bin/archived/memcached1.6.39/` +5. **Combines** binaries + configuration files +6. **Packages** the release archive +7. **Generates** hash files (MD5, SHA1, SHA256, SHA512) #### Directory Structure diff --git a/README.md b/README.md index 1d9e0b6..6acb950 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,8 @@ This project uses **Gradle** as its build system for building and packaging Memc ### Key Features +- **Automated Upstream Workflow**: Automatically creates upstream releases if version doesn't exist +- **Smart Version Detection**: Checks modules-untouched and creates releases as needed - **Automatic Downloads**: Fetches Memcached binaries from [modules-untouched](https://github.com/Bearsampp/modules-untouched) - **Local Configuration**: Bearsampp-specific configuration files stored in `bin/` directory - **Smart Caching**: Downloads are cached to speed up subsequent builds diff --git a/build.gradle b/build.gradle index b7a8315..c468df3 100644 --- a/build.gradle +++ b/build.gradle @@ -4,14 +4,15 @@ * This is a modern Gradle build configuration for the Memcached module. * * VERSION RESOLUTION STRATEGY: - * - Binaries are downloaded from modules-untouched repository - * - Configuration files (bearsampp.conf) are stored locally in bin/ - * - Build combines binaries + configuration into release packages + * - Checks if version exists in modules-untouched + * - If not, downloads binaries from nono303/memcached, creates release + * - Waits for modules-untouched to be updated by GitHub Action + * - Then downloads from modules-untouched and builds final packages * * Usage: * gradle tasks - List all available tasks - * gradle release -PbundleVersion=1.6.39 - Build release for specific version - * gradle releaseAll - Build releases for all available versions + * gradle release -PbundleVersion=1.6.39 - Build release (auto-handles upstream) + * gradle releaseAll - Build all available versions * gradle clean - Clean build artifacts * gradle info - Display build information * gradle verify - Verify build environment @@ -61,6 +62,7 @@ ext { bundleTmpBuildPath = file("${buildTmpPath}/bundles_build/${bundleType}/${bundleName}").absolutePath bundleTmpPrepPath = file("${buildTmpPath}/bundles_prep/${bundleType}/${bundleName}").absolutePath bundleTmpSrcPath = file("${buildTmpPath}/bundles_src").absolutePath + bundleTmpUpstreamPath = file("${buildTmpPath}/upstream/${bundleName}").absolutePath } // Configure repositories @@ -92,6 +94,300 @@ def fetchModulesUntouchedProperties() { } } +// Helper function to check if version exists in modules-untouched +def versionExistsInModulesUntouched(String version) { + def props = fetchModulesUntouchedProperties() + return props != null && props.getProperty(version) != null +} + +// Helper function to download memcached binaries from nono303 +def downloadMemcachedFromNono303(String version) { + println "" + println "=".multiply(70) + println "Downloading Memcached ${version} from nono303/memcached" + println "=".multiply(70) + println "" + + // Create upstream directory + def upstreamDir = file(bundleTmpUpstreamPath) + upstreamDir.mkdirs() + + // Repository details (matching the batch file) + def repoUrl = "https://github.com/nono303/memcached" + def branch = "master" + def subfolder = "libevent-2.1/x64" + + println "Repository: ${repoUrl}" + println "Branch: ${branch}" + println "Subfolder: ${subfolder}" + println "Version: ${version}" + println "" + + // Download repository as ZIP archive + def repoZipUrl = "${repoUrl}/archive/refs/heads/${branch}.zip" + def repoZipFile = file("${upstreamDir}/memcached-repo.zip") + + if (!repoZipFile.exists()) { + println "Downloading repository archive..." + println "URL: ${repoZipUrl}" + try { + def connection = new URL(repoZipUrl).openConnection() + connection.setRequestProperty("User-Agent", "Bearsampp-Build") + repoZipFile.withOutputStream { out -> + connection.inputStream.withCloseable { inp -> + out << inp + } + } + println "Download complete: ${repoZipFile}" + } catch (Exception e) { + throw new GradleException("Failed to download repository: ${e.message}\n\nURL: ${repoZipUrl}") + } + } else { + println "Using cached repository: ${repoZipFile}" + } + + // Extract the repository ZIP + def repoExtractDir = file("${upstreamDir}/repo-extract") + if (repoExtractDir.exists()) { + delete repoExtractDir + } + repoExtractDir.mkdirs() + + println "Extracting repository archive..." + copy { + from zipTree(repoZipFile) + into repoExtractDir + } + + // Find the extracted folder (will be named like "memcached-master") + def extractedRepoDir = repoExtractDir.listFiles()?.find { it.isDirectory() && it.name.startsWith('memcached-') } + if (!extractedRepoDir) { + throw new GradleException("Could not find extracted repository folder in ${repoExtractDir}") + } + + println "Found repository folder: ${extractedRepoDir.name}" + + // Locate the subfolder with the binaries + def sourceDir = file("${extractedRepoDir}/${subfolder}") + if (!sourceDir.exists()) { + throw new GradleException("Subfolder '${subfolder}' not found in repository at ${sourceDir}") + } + + println "Found binaries folder: ${sourceDir}" + + // Copy files to output directory + def outputDir = file("${upstreamDir}/memcached-${version}-files") + if (outputDir.exists()) { + delete outputDir + } + outputDir.mkdirs() + + println "Copying files from ${subfolder}..." + copy { + from sourceDir + into outputDir + include '**/*' + } + + // Count files + def fileCount = 0 + outputDir.eachFileRecurse { fileCount++ } + println "Successfully copied ${fileCount} files to ${outputDir}" + + // Verify memcached.exe exists + def memcachedExe = file("${outputDir}/memcached.exe") + if (!memcachedExe.exists()) { + throw new GradleException("memcached.exe not found in downloaded files at ${outputDir}") + } + + println "Verified memcached.exe exists" + println "" + + return outputDir +} + +// Helper function to get memcached.exe version +def getMemcachedVersion(File memcachedExe) { + if (!memcachedExe.exists()) { + throw new GradleException("memcached.exe not found: ${memcachedExe}") + } + + try { + // Use PowerShell to get file version + def command = [ + 'powershell', + '-NoProfile', + '-Command', + "(Get-Item '${memcachedExe.absolutePath}').VersionInfo.FileVersion" + ] + + def process = new ProcessBuilder(command as String[]) + .redirectErrorStream(true) + .start() + + def output = process.inputStream.text.trim() + def exitCode = process.waitFor() + + if (exitCode != 0 || !output) { + throw new GradleException("Failed to get version from memcached.exe") + } + + return output + } catch (Exception e) { + throw new GradleException("Failed to extract version from memcached.exe: ${e.message}") + } +} + +// Helper function to create upstream release +def createUpstreamRelease(String version, File binariesDir) { + println "" + println "=".multiply(70) + println "Creating Upstream Release" + println "=".multiply(70) + println "" + + // Get memcached version from exe + def memcachedExe = file("${binariesDir}/memcached.exe") + def memcachedVersion = getMemcachedVersion(memcachedExe) + println "Memcached version: ${memcachedVersion}" + + // Create 7z archive + def upstreamDir = file(bundleTmpUpstreamPath) + def archiveName = "memcached-${version}-win64.7z" + def archiveFile = file("${upstreamDir}/${archiveName}") + + if (archiveFile.exists()) { + delete archiveFile + } + + println "Creating archive: ${archiveName}" + + // Find 7z executable + def sevenZipExe = find7ZipExecutable() + if (!sevenZipExe) { + throw new GradleException("7-Zip not found. Required for creating upstream release.") + } + + // Create 7z archive from binaries directory + def command = [ + sevenZipExe, + 'a', + '-t7z', + '-mx9', + archiveFile.absolutePath.toString(), + '.' + ] + + def process = new ProcessBuilder(command as String[]) + .directory(binariesDir) + .redirectErrorStream(true) + .start() + + process.inputStream.eachLine { line -> + println " ${line}" + } + + def exitCode = process.waitFor() + if (exitCode != 0) { + throw new GradleException("7-Zip failed with exit code: ${exitCode}") + } + + println "Archive created: ${archiveFile}" + println "" + + // Create GitHub release in modules-untouched repository + def ghToken = System.getenv('GH_PAT') + if (!ghToken) { + throw new GradleException("GH_PAT environment variable not set. Required for creating GitHub releases.") + } + + def releaseTag = "memcached-${bundleRelease}" // e.g., "memcached-2025.8.20" + def releaseTitle = "Memcache ${memcachedVersion}" + def releaseBody = "Memcached ${memcachedVersion} automated build" + + println "Creating GitHub release in modules-untouched:" + println " Repository: Bearsampp/modules-untouched" + println " Tag: ${releaseTag}" + println " Title: ${releaseTitle}" + println " Asset: ${archiveName}" + println "" + + // Use GitHub CLI (gh) to create release in modules-untouched repository + def ghCommand = [ + 'gh', + 'release', + 'create', + releaseTag, + archiveFile.absolutePath, + '--repo', 'Bearsampp/modules-untouched', + '--title', releaseTitle, + '--notes', releaseBody + ] + + def ghProcess = new ProcessBuilder(ghCommand as String[]) + .directory(projectDir) + .redirectErrorStream(true) + .start() + + ghProcess.inputStream.eachLine { line -> + println " ${line}" + } + + def ghExitCode = ghProcess.waitFor() + if (ghExitCode != 0) { + throw new GradleException("GitHub release creation failed with exit code: ${ghExitCode}") + } + + println "" + println "GitHub release created successfully in modules-untouched!" + println "Release URL: https://github.com/Bearsampp/modules-untouched/releases/tag/${releaseTag}" + println "" + println "The GitHub Action will update memcached.properties with:" + println " ${version} = https://github.com/Bearsampp/modules-untouched/releases/download/${releaseTag}/${archiveName}" + println "" +} + +// Helper function to wait for modules-untouched to be updated +def waitForModulesUntouchedUpdate(String version, int maxWaitMinutes = 5) { + println "" + println "=".multiply(70) + println "Waiting for modules-untouched to be updated..." + println "=".multiply(70) + println "" + + def maxWaitMs = maxWaitMinutes * 60 * 1000 + def checkIntervalMs = 10 * 1000 // Check every 10 seconds + def startTime = System.currentTimeMillis() + def attempt = 0 + + while (System.currentTimeMillis() - startTime < maxWaitMs) { + attempt++ + def elapsed = (System.currentTimeMillis() - startTime) / 1000 + println "[${attempt}] Checking modules-untouched (${elapsed}s elapsed)..." + + if (versionExistsInModulesUntouched(version)) { + println "" + println "✓ Version ${version} found in modules-untouched!" + println "" + return true + } + + println " Not yet available, waiting ${checkIntervalMs / 1000}s..." + Thread.sleep(checkIntervalMs) + } + + throw new GradleException(""" + Timeout waiting for modules-untouched to be updated. + + Version ${version} was not found after ${maxWaitMinutes} minutes. + + Please check: + 1. GitHub Action status: https://github.com/Bearsampp/modules-untouched/actions + 2. The release was created: https://github.com/Bearsampp/module-memcached/releases + 3. Try running the build again in a few minutes + """.stripIndent()) +} + // Helper function to get module from modules-untouched (downloads and extracts binaries) def getModuleUntouched(String version) { println "** Get Module Untouched" @@ -352,6 +648,7 @@ tasks.register('info') { Tmp Prep: ${bundleTmpPrepPath} Tmp Build: ${bundleTmpBuildPath} Tmp Src: ${bundleTmpSrcPath} + Tmp Upstream: ${bundleTmpUpstreamPath} Java: Version: ${JavaVersion.current()} @@ -364,7 +661,7 @@ tasks.register('info') { Quick Start: gradle tasks - List all available tasks gradle info - Show this information - gradle release -PbundleVersion=1.6.39 - Build release for version + gradle release -PbundleVersion=1.6.39 - Build release (auto-handles upstream) gradle releaseAll - Build all available versions gradle clean - Clean build artifacts gradle verify - Verify build environment @@ -374,10 +671,10 @@ tasks.register('info') { } } -// Task: Main release task - build release for specific version +// Task: Main release task - build release for specific version (with upstream handling) tasks.register('release') { group = 'build' - description = 'Build release package for a specific version (use -PbundleVersion=X.X.X or run interactively)' + description = 'Build release package for a specific version (auto-handles upstream if needed)' doLast { def versionToBuild = project.findProperty('bundleVersion') @@ -460,6 +757,35 @@ tasks.register('release') { println "=".multiply(70) println "" + // Check if version exists in modules-untouched + println "Checking modules-untouched for version ${versionToBuild}..." + def existsInUntouched = versionExistsInModulesUntouched(versionToBuild) + + if (!existsInUntouched) { + println "" + println "⚠ Version ${versionToBuild} not found in modules-untouched" + println "Starting upstream release process..." + println "" + + // Download binaries from nono303 + def binariesDir = downloadMemcachedFromNono303(versionToBuild) + + // Create upstream release (7z + GitHub release) + createUpstreamRelease(versionToBuild, binariesDir) + + // Wait for modules-untouched to be updated + waitForModulesUntouchedUpdate(versionToBuild, 5) + } else { + println "✓ Version ${versionToBuild} found in modules-untouched" + println "" + } + + // Now continue with normal release process + println "=".multiply(70) + println "Continuing with normal release process" + println "=".multiply(70) + println "" + // Get module binaries from modules-untouched (downloads and extracts if needed) def bundleSrcFinal = getModuleUntouched(versionToBuild) @@ -725,6 +1051,18 @@ tasks.register('verify') { def untouchedProps = fetchModulesUntouchedProperties() checks['modules-untouched access'] = untouchedProps != null + // Check GitHub CLI + try { + def ghProcess = ['gh', '--version'].execute() + ghProcess.waitFor() + checks['GitHub CLI (gh)'] = ghProcess.exitValue() == 0 + } catch (Exception e) { + checks['GitHub CLI (gh)'] = false + } + + // Check GH_PAT environment variable + checks['GH_PAT environment variable'] = System.getenv('GH_PAT') != null + println "\nEnvironment Check Results:" println "-".multiply(60) checks.each { name, passed -> @@ -737,10 +1075,21 @@ tasks.register('verify') { if (allPassed) { println "\n[SUCCESS] All checks passed! Build environment is ready." println "\nYou can now run:" - println " gradle release -PbundleVersion=1.6.39 - Build release for version" + println " gradle release -PbundleVersion=1.6.39 - Build release (auto-handles upstream)" println " gradle listVersions - List available versions" } else { println "\n[WARNING] Some checks failed. Please review the requirements." + + if (!checks['GitHub CLI (gh)']) { + println "\nTo install GitHub CLI:" + println " https://cli.github.com/" + } + + if (!checks['GH_PAT environment variable']) { + println "\nTo set GH_PAT:" + println " \$env:GH_PAT = 'your_github_token'" + } + throw new GradleException("Build environment verification failed") } } @@ -836,9 +1185,11 @@ tasks.register('checkModulesUntouched') { println "=".multiply(70) println "" println "Build Process:" - println " 1. Binaries downloaded from modules-untouched" - println " 2. Configuration files (bearsampp.conf) from local bin/" - println " 3. Combined into release package" + println " 1. Check if version exists in modules-untouched" + println " 2. If not: Download from nono303 → Create release → Wait for update" + println " 3. Download binaries from modules-untouched" + println " 4. Overlay configuration files from local bin/" + println " 5. Package into final release" println "" } else { From 1613eeda8770bf6ff075204c7d78b1ff0a7602af Mon Sep 17 00:00:00 2001 From: Bear Date: Tue, 16 Dec 2025 23:48:38 -0600 Subject: [PATCH 2/2] revert to remove auto-build --- build.gradle | 375 ++------------------------------------------------- 1 file changed, 12 insertions(+), 363 deletions(-) diff --git a/build.gradle b/build.gradle index c468df3..b7a8315 100644 --- a/build.gradle +++ b/build.gradle @@ -4,15 +4,14 @@ * This is a modern Gradle build configuration for the Memcached module. * * VERSION RESOLUTION STRATEGY: - * - Checks if version exists in modules-untouched - * - If not, downloads binaries from nono303/memcached, creates release - * - Waits for modules-untouched to be updated by GitHub Action - * - Then downloads from modules-untouched and builds final packages + * - Binaries are downloaded from modules-untouched repository + * - Configuration files (bearsampp.conf) are stored locally in bin/ + * - Build combines binaries + configuration into release packages * * Usage: * gradle tasks - List all available tasks - * gradle release -PbundleVersion=1.6.39 - Build release (auto-handles upstream) - * gradle releaseAll - Build all available versions + * gradle release -PbundleVersion=1.6.39 - Build release for specific version + * gradle releaseAll - Build releases for all available versions * gradle clean - Clean build artifacts * gradle info - Display build information * gradle verify - Verify build environment @@ -62,7 +61,6 @@ ext { bundleTmpBuildPath = file("${buildTmpPath}/bundles_build/${bundleType}/${bundleName}").absolutePath bundleTmpPrepPath = file("${buildTmpPath}/bundles_prep/${bundleType}/${bundleName}").absolutePath bundleTmpSrcPath = file("${buildTmpPath}/bundles_src").absolutePath - bundleTmpUpstreamPath = file("${buildTmpPath}/upstream/${bundleName}").absolutePath } // Configure repositories @@ -94,300 +92,6 @@ def fetchModulesUntouchedProperties() { } } -// Helper function to check if version exists in modules-untouched -def versionExistsInModulesUntouched(String version) { - def props = fetchModulesUntouchedProperties() - return props != null && props.getProperty(version) != null -} - -// Helper function to download memcached binaries from nono303 -def downloadMemcachedFromNono303(String version) { - println "" - println "=".multiply(70) - println "Downloading Memcached ${version} from nono303/memcached" - println "=".multiply(70) - println "" - - // Create upstream directory - def upstreamDir = file(bundleTmpUpstreamPath) - upstreamDir.mkdirs() - - // Repository details (matching the batch file) - def repoUrl = "https://github.com/nono303/memcached" - def branch = "master" - def subfolder = "libevent-2.1/x64" - - println "Repository: ${repoUrl}" - println "Branch: ${branch}" - println "Subfolder: ${subfolder}" - println "Version: ${version}" - println "" - - // Download repository as ZIP archive - def repoZipUrl = "${repoUrl}/archive/refs/heads/${branch}.zip" - def repoZipFile = file("${upstreamDir}/memcached-repo.zip") - - if (!repoZipFile.exists()) { - println "Downloading repository archive..." - println "URL: ${repoZipUrl}" - try { - def connection = new URL(repoZipUrl).openConnection() - connection.setRequestProperty("User-Agent", "Bearsampp-Build") - repoZipFile.withOutputStream { out -> - connection.inputStream.withCloseable { inp -> - out << inp - } - } - println "Download complete: ${repoZipFile}" - } catch (Exception e) { - throw new GradleException("Failed to download repository: ${e.message}\n\nURL: ${repoZipUrl}") - } - } else { - println "Using cached repository: ${repoZipFile}" - } - - // Extract the repository ZIP - def repoExtractDir = file("${upstreamDir}/repo-extract") - if (repoExtractDir.exists()) { - delete repoExtractDir - } - repoExtractDir.mkdirs() - - println "Extracting repository archive..." - copy { - from zipTree(repoZipFile) - into repoExtractDir - } - - // Find the extracted folder (will be named like "memcached-master") - def extractedRepoDir = repoExtractDir.listFiles()?.find { it.isDirectory() && it.name.startsWith('memcached-') } - if (!extractedRepoDir) { - throw new GradleException("Could not find extracted repository folder in ${repoExtractDir}") - } - - println "Found repository folder: ${extractedRepoDir.name}" - - // Locate the subfolder with the binaries - def sourceDir = file("${extractedRepoDir}/${subfolder}") - if (!sourceDir.exists()) { - throw new GradleException("Subfolder '${subfolder}' not found in repository at ${sourceDir}") - } - - println "Found binaries folder: ${sourceDir}" - - // Copy files to output directory - def outputDir = file("${upstreamDir}/memcached-${version}-files") - if (outputDir.exists()) { - delete outputDir - } - outputDir.mkdirs() - - println "Copying files from ${subfolder}..." - copy { - from sourceDir - into outputDir - include '**/*' - } - - // Count files - def fileCount = 0 - outputDir.eachFileRecurse { fileCount++ } - println "Successfully copied ${fileCount} files to ${outputDir}" - - // Verify memcached.exe exists - def memcachedExe = file("${outputDir}/memcached.exe") - if (!memcachedExe.exists()) { - throw new GradleException("memcached.exe not found in downloaded files at ${outputDir}") - } - - println "Verified memcached.exe exists" - println "" - - return outputDir -} - -// Helper function to get memcached.exe version -def getMemcachedVersion(File memcachedExe) { - if (!memcachedExe.exists()) { - throw new GradleException("memcached.exe not found: ${memcachedExe}") - } - - try { - // Use PowerShell to get file version - def command = [ - 'powershell', - '-NoProfile', - '-Command', - "(Get-Item '${memcachedExe.absolutePath}').VersionInfo.FileVersion" - ] - - def process = new ProcessBuilder(command as String[]) - .redirectErrorStream(true) - .start() - - def output = process.inputStream.text.trim() - def exitCode = process.waitFor() - - if (exitCode != 0 || !output) { - throw new GradleException("Failed to get version from memcached.exe") - } - - return output - } catch (Exception e) { - throw new GradleException("Failed to extract version from memcached.exe: ${e.message}") - } -} - -// Helper function to create upstream release -def createUpstreamRelease(String version, File binariesDir) { - println "" - println "=".multiply(70) - println "Creating Upstream Release" - println "=".multiply(70) - println "" - - // Get memcached version from exe - def memcachedExe = file("${binariesDir}/memcached.exe") - def memcachedVersion = getMemcachedVersion(memcachedExe) - println "Memcached version: ${memcachedVersion}" - - // Create 7z archive - def upstreamDir = file(bundleTmpUpstreamPath) - def archiveName = "memcached-${version}-win64.7z" - def archiveFile = file("${upstreamDir}/${archiveName}") - - if (archiveFile.exists()) { - delete archiveFile - } - - println "Creating archive: ${archiveName}" - - // Find 7z executable - def sevenZipExe = find7ZipExecutable() - if (!sevenZipExe) { - throw new GradleException("7-Zip not found. Required for creating upstream release.") - } - - // Create 7z archive from binaries directory - def command = [ - sevenZipExe, - 'a', - '-t7z', - '-mx9', - archiveFile.absolutePath.toString(), - '.' - ] - - def process = new ProcessBuilder(command as String[]) - .directory(binariesDir) - .redirectErrorStream(true) - .start() - - process.inputStream.eachLine { line -> - println " ${line}" - } - - def exitCode = process.waitFor() - if (exitCode != 0) { - throw new GradleException("7-Zip failed with exit code: ${exitCode}") - } - - println "Archive created: ${archiveFile}" - println "" - - // Create GitHub release in modules-untouched repository - def ghToken = System.getenv('GH_PAT') - if (!ghToken) { - throw new GradleException("GH_PAT environment variable not set. Required for creating GitHub releases.") - } - - def releaseTag = "memcached-${bundleRelease}" // e.g., "memcached-2025.8.20" - def releaseTitle = "Memcache ${memcachedVersion}" - def releaseBody = "Memcached ${memcachedVersion} automated build" - - println "Creating GitHub release in modules-untouched:" - println " Repository: Bearsampp/modules-untouched" - println " Tag: ${releaseTag}" - println " Title: ${releaseTitle}" - println " Asset: ${archiveName}" - println "" - - // Use GitHub CLI (gh) to create release in modules-untouched repository - def ghCommand = [ - 'gh', - 'release', - 'create', - releaseTag, - archiveFile.absolutePath, - '--repo', 'Bearsampp/modules-untouched', - '--title', releaseTitle, - '--notes', releaseBody - ] - - def ghProcess = new ProcessBuilder(ghCommand as String[]) - .directory(projectDir) - .redirectErrorStream(true) - .start() - - ghProcess.inputStream.eachLine { line -> - println " ${line}" - } - - def ghExitCode = ghProcess.waitFor() - if (ghExitCode != 0) { - throw new GradleException("GitHub release creation failed with exit code: ${ghExitCode}") - } - - println "" - println "GitHub release created successfully in modules-untouched!" - println "Release URL: https://github.com/Bearsampp/modules-untouched/releases/tag/${releaseTag}" - println "" - println "The GitHub Action will update memcached.properties with:" - println " ${version} = https://github.com/Bearsampp/modules-untouched/releases/download/${releaseTag}/${archiveName}" - println "" -} - -// Helper function to wait for modules-untouched to be updated -def waitForModulesUntouchedUpdate(String version, int maxWaitMinutes = 5) { - println "" - println "=".multiply(70) - println "Waiting for modules-untouched to be updated..." - println "=".multiply(70) - println "" - - def maxWaitMs = maxWaitMinutes * 60 * 1000 - def checkIntervalMs = 10 * 1000 // Check every 10 seconds - def startTime = System.currentTimeMillis() - def attempt = 0 - - while (System.currentTimeMillis() - startTime < maxWaitMs) { - attempt++ - def elapsed = (System.currentTimeMillis() - startTime) / 1000 - println "[${attempt}] Checking modules-untouched (${elapsed}s elapsed)..." - - if (versionExistsInModulesUntouched(version)) { - println "" - println "✓ Version ${version} found in modules-untouched!" - println "" - return true - } - - println " Not yet available, waiting ${checkIntervalMs / 1000}s..." - Thread.sleep(checkIntervalMs) - } - - throw new GradleException(""" - Timeout waiting for modules-untouched to be updated. - - Version ${version} was not found after ${maxWaitMinutes} minutes. - - Please check: - 1. GitHub Action status: https://github.com/Bearsampp/modules-untouched/actions - 2. The release was created: https://github.com/Bearsampp/module-memcached/releases - 3. Try running the build again in a few minutes - """.stripIndent()) -} - // Helper function to get module from modules-untouched (downloads and extracts binaries) def getModuleUntouched(String version) { println "** Get Module Untouched" @@ -648,7 +352,6 @@ tasks.register('info') { Tmp Prep: ${bundleTmpPrepPath} Tmp Build: ${bundleTmpBuildPath} Tmp Src: ${bundleTmpSrcPath} - Tmp Upstream: ${bundleTmpUpstreamPath} Java: Version: ${JavaVersion.current()} @@ -661,7 +364,7 @@ tasks.register('info') { Quick Start: gradle tasks - List all available tasks gradle info - Show this information - gradle release -PbundleVersion=1.6.39 - Build release (auto-handles upstream) + gradle release -PbundleVersion=1.6.39 - Build release for version gradle releaseAll - Build all available versions gradle clean - Clean build artifacts gradle verify - Verify build environment @@ -671,10 +374,10 @@ tasks.register('info') { } } -// Task: Main release task - build release for specific version (with upstream handling) +// Task: Main release task - build release for specific version tasks.register('release') { group = 'build' - description = 'Build release package for a specific version (auto-handles upstream if needed)' + description = 'Build release package for a specific version (use -PbundleVersion=X.X.X or run interactively)' doLast { def versionToBuild = project.findProperty('bundleVersion') @@ -757,35 +460,6 @@ tasks.register('release') { println "=".multiply(70) println "" - // Check if version exists in modules-untouched - println "Checking modules-untouched for version ${versionToBuild}..." - def existsInUntouched = versionExistsInModulesUntouched(versionToBuild) - - if (!existsInUntouched) { - println "" - println "⚠ Version ${versionToBuild} not found in modules-untouched" - println "Starting upstream release process..." - println "" - - // Download binaries from nono303 - def binariesDir = downloadMemcachedFromNono303(versionToBuild) - - // Create upstream release (7z + GitHub release) - createUpstreamRelease(versionToBuild, binariesDir) - - // Wait for modules-untouched to be updated - waitForModulesUntouchedUpdate(versionToBuild, 5) - } else { - println "✓ Version ${versionToBuild} found in modules-untouched" - println "" - } - - // Now continue with normal release process - println "=".multiply(70) - println "Continuing with normal release process" - println "=".multiply(70) - println "" - // Get module binaries from modules-untouched (downloads and extracts if needed) def bundleSrcFinal = getModuleUntouched(versionToBuild) @@ -1051,18 +725,6 @@ tasks.register('verify') { def untouchedProps = fetchModulesUntouchedProperties() checks['modules-untouched access'] = untouchedProps != null - // Check GitHub CLI - try { - def ghProcess = ['gh', '--version'].execute() - ghProcess.waitFor() - checks['GitHub CLI (gh)'] = ghProcess.exitValue() == 0 - } catch (Exception e) { - checks['GitHub CLI (gh)'] = false - } - - // Check GH_PAT environment variable - checks['GH_PAT environment variable'] = System.getenv('GH_PAT') != null - println "\nEnvironment Check Results:" println "-".multiply(60) checks.each { name, passed -> @@ -1075,21 +737,10 @@ tasks.register('verify') { if (allPassed) { println "\n[SUCCESS] All checks passed! Build environment is ready." println "\nYou can now run:" - println " gradle release -PbundleVersion=1.6.39 - Build release (auto-handles upstream)" + println " gradle release -PbundleVersion=1.6.39 - Build release for version" println " gradle listVersions - List available versions" } else { println "\n[WARNING] Some checks failed. Please review the requirements." - - if (!checks['GitHub CLI (gh)']) { - println "\nTo install GitHub CLI:" - println " https://cli.github.com/" - } - - if (!checks['GH_PAT environment variable']) { - println "\nTo set GH_PAT:" - println " \$env:GH_PAT = 'your_github_token'" - } - throw new GradleException("Build environment verification failed") } } @@ -1185,11 +836,9 @@ tasks.register('checkModulesUntouched') { println "=".multiply(70) println "" println "Build Process:" - println " 1. Check if version exists in modules-untouched" - println " 2. If not: Download from nono303 → Create release → Wait for update" - println " 3. Download binaries from modules-untouched" - println " 4. Overlay configuration files from local bin/" - println " 5. Package into final release" + println " 1. Binaries downloaded from modules-untouched" + println " 2. Configuration files (bearsampp.conf) from local bin/" + println " 3. Combined into release package" println "" } else {