diff --git a/bin/memcached1.6.41/bearsampp.conf b/bin/memcached1.6.41/bearsampp.conf new file mode 100644 index 0000000..44afaaf --- /dev/null +++ b/bin/memcached1.6.41/bearsampp.conf @@ -0,0 +1,6 @@ +memcachedVersion = "1.6.41" +memcachedExe = "memcached.exe" +memcachedMemory = "512" +memcachedPort = "11211" + +bundleRelease = "@RELEASE_VERSION@" diff --git a/build.gradle b/build.gradle index b7a8315..7c30834 100644 --- a/build.gradle +++ b/build.gradle @@ -74,10 +74,12 @@ repositories { // Helper function to fetch modules-untouched properties def fetchModulesUntouchedProperties() { - def propsUrl = "https://raw.githubusercontent.com/Bearsampp/modules-untouched/main/modules/memcached.properties" + // Use GitHub API to get the file content (no CDN caching) + def apiUrl = "https://api.github.com/repos/Bearsampp/modules-untouched/contents/modules/memcached.properties?ref=main" try { - def connection = new URL(propsUrl).openConnection() + def connection = new URL(apiUrl).openConnection() connection.setRequestProperty("User-Agent", "Bearsampp-Build") + connection.setRequestProperty("Accept", "application/vnd.github.v3.raw") connection.setConnectTimeout(5000) connection.setReadTimeout(5000) @@ -92,6 +94,397 @@ def fetchModulesUntouchedProperties() { } } +// Helper function to create upstream release in modules-untouched +def createUpstreamRelease(String version) { + println "" + println "=".multiply(70) + println "Version ${version} not found in modules-untouched" + println "Starting upstream release process..." + println "=".multiply(70) + println "" + + // Check prerequisites + def ghPath = findGitHubCLI() + if (!ghPath) { + throw new GradleException(""" + GitHub CLI (gh) not found! + + Please install GitHub CLI to create upstream releases: + https://cli.github.com/ + + Or set GH_PATH environment variable to gh.exe location. + + Alternatively, manually create the release in modules-untouched repository. + """.stripIndent()) + } + + def sevenZipExe = find7ZipExecutable() + if (!sevenZipExe) { + throw new GradleException("7-Zip executable not found! Please install 7-Zip.") + } + + // Create temporary directory for upstream build + def upstreamTmpPath = file("${bundleTmpSrcPath}/upstream-${version}") + if (upstreamTmpPath.exists()) { + delete upstreamTmpPath + } + upstreamTmpPath.mkdirs() + + println "=".multiply(70) + println "Downloading Memcached ${version} from nono303/memcached" + println "=".multiply(70) + println "Repository: https://github.com/nono303/memcached" + println "Branch: master" + println "Subfolder: libevent-2.1/x64" + println "" + + // Clone nono303/memcached repository (sparse checkout for libevent-2.1/x64 only) + def cloneDir = file("${upstreamTmpPath}/nono303-memcached") + + try { + // Initialize git repo with sparse checkout + def initCmd = ['git', 'init', cloneDir.absolutePath] + def initProcess = new ProcessBuilder(initCmd as String[]) + .redirectErrorStream(true) + .start() + initProcess.waitFor() + + // Configure sparse checkout + def sparseCmd = ['git', '-C', cloneDir.absolutePath, 'config', 'core.sparseCheckout', 'true'] + def sparseProcess = new ProcessBuilder(sparseCmd as String[]) + .redirectErrorStream(true) + .start() + sparseProcess.waitFor() + + // Set sparse checkout path + def sparseFile = file("${cloneDir}/.git/info/sparse-checkout") + sparseFile.parentFile.mkdirs() + sparseFile.text = "libevent-2.1/x64/*\n" + + // Add remote + def remoteCmd = ['git', '-C', cloneDir.absolutePath, 'remote', 'add', 'origin', 'https://github.com/nono303/memcached.git'] + def remoteProcess = new ProcessBuilder(remoteCmd as String[]) + .redirectErrorStream(true) + .start() + remoteProcess.waitFor() + + // Pull the specific folder + println "Downloading files from nono303/memcached..." + def pullCmd = ['git', '-C', cloneDir.absolutePath, 'pull', 'origin', 'master'] + def pullProcess = new ProcessBuilder(pullCmd as String[]) + .redirectErrorStream(true) + .start() + + pullProcess.inputStream.eachLine { line -> + println " ${line}" + } + + def exitCode = pullProcess.waitFor() + if (exitCode != 0) { + throw new GradleException("Git pull failed with exit code: ${exitCode}") + } + + } catch (Exception e) { + throw new GradleException("Failed to download from nono303/memcached: ${e.message}") + } + + // Copy files from libevent-2.1/x64 to build directory + def sourceDir = file("${cloneDir}/libevent-2.1/x64") + def buildDir = file("${upstreamTmpPath}/memcached-${version}-win64") + + if (!sourceDir.exists()) { + throw new GradleException("Source directory not found: ${sourceDir}") + } + + println "" + println "Copying files from libevent-2.1/x64..." + copy { + from sourceDir + into buildDir + exclude '.git', '.gitignore' + } + + def fileCount = buildDir.listFiles()?.size() ?: 0 + println "Successfully copied ${fileCount} files" + + // Extract version from memcached.exe + def memcachedExe = file("${buildDir}/memcached.exe") + if (!memcachedExe.exists()) { + throw new GradleException("memcached.exe not found in downloaded files") + } + + println "" + println "=".multiply(70) + println "Creating Upstream Release" + println "=".multiply(70) + println "Memcached version: ${version}" + + // Create 7z archive with correct naming convention + // Format: memcached-{version}.7z (files at root, not in subdirectory) + def archiveName = "memcached-${version}.7z" + def archiveFile = file("${upstreamTmpPath}/${archiveName}") + + println "Creating archive: ${archiveName}" + + // Archive the contents of the directory (not the directory itself) + def command = [ + sevenZipExe, + 'a', + '-t7z', + '-mx9', + archiveFile.absolutePath, + '.' + ] + + def process = new ProcessBuilder(command as String[]) + .directory(buildDir) // Run from inside the directory to archive contents + .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}" + + // Create GitHub release + println "" + println "Creating GitHub release:" + + // Generate tag in format: memcached-YYYY.MM.DD + def today = new Date() + def tagDateFormat = new java.text.SimpleDateFormat('yyyy.MM.dd') + def releaseTag = "memcached-${tagDateFormat.format(today)}" + def releaseTitle = "Memcache ${version}" + def releaseBody = "Memcached ${version} automated build" + + println " Tag: ${releaseTag}" + println " Title: ${releaseTitle}" + println " Asset: ${archiveName}" + println "" + + // Check for GH_PAT environment variable + def ghToken = System.getenv('GH_PAT') + if (!ghToken) { + throw new GradleException(""" + GH_PAT environment variable not set! + + Please set your GitHub Personal Access Token: + Windows (PowerShell): \$env:GH_PAT = 'your_token_here' + Windows (CMD): set GH_PAT=your_token_here + + The token needs 'repo' scope to create releases. + Create one at: https://github.com/settings/tokens + """.stripIndent()) + } + + // Check if release already exists + def checkCmd = [ + ghPath, + 'release', + 'view', + releaseTag, + '--repo', 'Bearsampp/modules-untouched' + ] + + def checkBuilder = new ProcessBuilder(checkCmd as String[]) + .redirectErrorStream(true) + + // Set GH_TOKEN environment variable + checkBuilder.environment().put('GH_TOKEN', ghToken) + + def checkProcess = checkBuilder.start() + def releaseExists = checkProcess.waitFor() == 0 + + if (releaseExists) { + println "Release ${releaseTag} already exists, deleting and recreating..." + + // Delete the existing release + def deleteCmd = [ + ghPath, + 'release', + 'delete', + releaseTag, + '--repo', 'Bearsampp/modules-untouched', + '--yes' + ] + + def deleteBuilder = new ProcessBuilder(deleteCmd as String[]) + .redirectErrorStream(true) + + // Set GH_TOKEN environment variable + deleteBuilder.environment().put('GH_TOKEN', ghToken) + + def deleteProcess = deleteBuilder.start() + + deleteProcess.inputStream.eachLine { line -> + println " ${line}" + } + + exitCode = deleteProcess.waitFor() + if (exitCode != 0) { + println "Warning: Failed to delete existing release (exit code: ${exitCode})" + } + + // Wait a moment for GitHub to process the deletion + Thread.sleep(2000) + + // Now create the release fresh + def createCmd = [ + ghPath, + 'release', + 'create', + releaseTag, + archiveFile.absolutePath, + '--repo', 'Bearsampp/modules-untouched', + '--title', releaseTitle, + '--notes', releaseBody, + '--latest' + ] + + def createBuilder = new ProcessBuilder(createCmd as String[]) + .redirectErrorStream(true) + + // Set GH_TOKEN environment variable + createBuilder.environment().put('GH_TOKEN', ghToken) + + def createProcess = createBuilder.start() + + createProcess.inputStream.eachLine { line -> + println " ${line}" + } + + exitCode = createProcess.waitFor() + if (exitCode != 0) { + throw new GradleException("GitHub release creation failed with exit code: ${exitCode}") + } + + } else { + // Create new release + def createCmd = [ + ghPath, + 'release', + 'create', + releaseTag, + archiveFile.absolutePath, + '--repo', 'Bearsampp/modules-untouched', + '--title', releaseTitle, + '--notes', releaseBody, + '--latest' + ] + + def createBuilder = new ProcessBuilder(createCmd as String[]) + .redirectErrorStream(true) + + // Set GH_TOKEN environment variable + createBuilder.environment().put('GH_TOKEN', ghToken) + + def createProcess = createBuilder.start() + + createProcess.inputStream.eachLine { line -> + println " ${line}" + } + + exitCode = createProcess.waitFor() + if (exitCode != 0) { + throw new GradleException("GitHub release creation failed with exit code: ${exitCode}") + } + } + + println "GitHub release created successfully!" + + // Wait for modules-untouched to be updated + println "" + println "=".multiply(70) + println "Waiting for modules-untouched to be updated..." + println "=".multiply(70) + + // Give the workflow time to start and complete + println "Waiting 30 seconds for workflow to complete..." + Thread.sleep(30000) + + def maxWaitTime = 300 // 5 minutes + def checkInterval = 15 // 15 seconds + def elapsed = 30 // Already waited 30 seconds + def found = false + + while (elapsed < maxWaitTime && !found) { + println "[${(elapsed / 15).intValue()}] Checking modules-untouched (${elapsed}s elapsed)..." + + def props = fetchModulesUntouchedProperties() + if (props && props.getProperty(version)) { + println "✓ Version ${version} found in modules-untouched!" + found = true + } else { + println " Not yet available, waiting ${checkInterval}s..." + Thread.sleep(checkInterval * 1000) + elapsed += checkInterval + } + } + + if (!found) { + throw new GradleException(""" + Timeout waiting for modules-untouched to be updated. + + The release was created successfully, but the properties file hasn't been updated yet. + Please wait a few minutes and try running the build again. + """.stripIndent()) + } + + println "" + println "=".multiply(70) + println "Continuing with normal release process" + println "=".multiply(70) + println "" +} + +// Helper function to find GitHub CLI executable +def findGitHubCLI() { + // Check environment variable + def ghPath = System.getenv('GH_PATH') + if (ghPath) { + def exe = file(ghPath) + if (exe.exists()) { + return exe.absolutePath + } + } + + // Try to find in PATH + try { + def process = ['where', 'gh.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 + } + + // Check common installation paths + def commonPaths = [ + 'C:/Program Files/GitHub CLI/gh.exe', + 'C:/Program Files (x86)/GitHub CLI/gh.exe', + "${System.getProperty('user.home')}/AppData/Local/Programs/GitHub CLI/gh.exe" + ] + + for (path in commonPaths) { + def exe = file(path) + if (exe.exists()) { + return exe.absolutePath + } + } + + return null +} + // Helper function to get module from modules-untouched (downloads and extracts binaries) def getModuleUntouched(String version) { println "** Get Module Untouched" @@ -104,10 +497,31 @@ def getModuleUntouched(String version) { throw new GradleException("Could not fetch modules-untouched properties for version ${version}") } + // Debug: print all available versions + println "* Available versions in properties file:" + untouchedProps.each { key, value -> + println " - ${key}" + } + // Get the URL for this version def downloadUrl = untouchedProps.getProperty(version) if (!downloadUrl) { - throw new GradleException("Version ${version} not found in modules-untouched properties") + // Version not found - try to create upstream release + println "" + println "⚠ Version ${version} not found in modules-untouched" + + createUpstreamRelease(version) + + // Fetch properties again after creating release + untouchedProps = fetchModulesUntouchedProperties() + if (!untouchedProps) { + throw new GradleException("Could not fetch modules-untouched properties after creating release") + } + + downloadUrl = untouchedProps.getProperty(version) + if (!downloadUrl) { + throw new GradleException("Version ${version} still not found in modules-untouched properties after creating release") + } } println "* Url : ${downloadUrl}"