diff --git a/.gitignore b/.gitignore index 207bc8a7..f0373c61 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,8 @@ # Qodo /.qodo + +# Gradle +.gradle/ +build/ +.gradle-cache/ diff --git a/.gradle-docs/API.md b/.gradle-docs/API.md new file mode 100644 index 00000000..7c858195 --- /dev/null +++ b/.gradle-docs/API.md @@ -0,0 +1,744 @@ +# API Reference + +Complete API reference for the Bearsampp Module PHP Gradle build system. + +--- + +## Table of Contents + +- [Build Script API](#build-script-api) +- [Helper Functions](#helper-functions) +- [Extension Points](#extension-points) +- [Properties API](#properties-api) +- [Task API](#task-api) + +--- + +## Build Script API + +### Project Configuration + +#### `group` + +**Type:** `String` +**Default:** `com.bearsampp.modules` +**Description:** Maven group ID for the project + +```groovy +group = 'com.bearsampp.modules' +``` + +--- + +#### `version` + +**Type:** `String` +**Default:** Value from `build.properties` +**Description:** Project version + +```groovy +version = buildProps.getProperty('bundle.release', '1.0.0') +``` + +--- + +#### `description` + +**Type:** `String` +**Default:** Generated from bundle name +**Description:** Project description + +```groovy +description = "Bearsampp Module - ${buildProps.getProperty('bundle.name', 'php')}" +``` + +--- + +### Extension Properties + +#### `ext.projectBasedir` + +**Type:** `String` +**Description:** Absolute path to project directory + +```groovy +ext.projectBasedir = projectDir.absolutePath +``` + +--- + +#### `ext.rootDir` + +**Type:** `String` +**Description:** Absolute path to parent directory + +```groovy +ext.rootDir = projectDir.parent +``` + +--- + +#### `ext.devPath` + +**Type:** `String` +**Description:** Absolute path to dev directory + +```groovy +ext.devPath = file("${rootDir}/dev").absolutePath +``` + +--- + +#### `ext.bundleName` + +**Type:** `String` +**Default:** `php` +**Description:** Name of the bundle from build.properties + +```groovy +ext.bundleName = buildProps.getProperty('bundle.name', 'php') +``` + +--- + +#### `ext.bundleRelease` + +**Type:** `String` +**Default:** `1.0.0` +**Description:** Release version from build.properties + +```groovy +ext.bundleRelease = buildProps.getProperty('bundle.release', '1.0.0') +``` + +--- + +#### `ext.bundleType` + +**Type:** `String` +**Default:** `bins` +**Description:** Bundle type from build.properties + +```groovy +ext.bundleType = buildProps.getProperty('bundle.type', 'bins') +``` + +--- + +#### `ext.bundleFormat` + +**Type:** `String` +**Default:** `7z` +**Description:** Archive format from build.properties + +```groovy +ext.bundleFormat = buildProps.getProperty('bundle.format', '7z') +``` + +--- + +#### `ext.phpExtPath` + +**Type:** `String` +**Description:** Absolute path to PHP extensions directory + +```groovy +ext.phpExtPath = file("${projectDir}/ext").absolutePath +``` + +--- + +#### `ext.pearInstallPath` + +**Type:** `String` +**Description:** Absolute path to PEAR installation directory + +```groovy +ext.pearInstallPath = file("${projectDir}/pear").absolutePath +``` + +--- + +#### `ext.bundleTmpPrepPath` + +**Type:** `String` +**Description:** Absolute path to temporary preparation directory + +```groovy +ext.bundleTmpPrepPath = file("${projectDir}/tmp/prep").absolutePath +``` + +--- + +#### `ext.buildTmpPath` + +**Type:** `String` +**Description:** Absolute path to temporary build directory + +```groovy +ext.buildTmpPath = file("${projectDir}/tmp").absolutePath +``` + +--- + +## Helper Functions + +### `downloadFile(String url, File destDir)` + +**Description:** Download a file from URL to destination directory + +**Parameters:** + +| Parameter | Type | Description | +|--------------|----------|--------------------------------------| +| `url` | String | URL to download from | +| `destDir` | File | Destination directory | + +**Returns:** `File` - Downloaded file + +**Example:** + +```groovy +def archive = downloadFile( + 'https://example.com/file.zip', + file("${buildTmpPath}/downloads") +) +``` + +**Process:** +1. Extract filename from URL +2. Check if file already exists +3. If not exists, download using URL.withInputStream +4. Return File object + +--- + +### `extractArchive(File archive, File destDir)` + +**Description:** Extract archive (ZIP or 7z) to destination directory + +**Parameters:** + +| Parameter | Type | Description | +|--------------|----------|--------------------------------------| +| `archive` | File | Archive file to extract | +| `destDir` | File | Destination directory | + +**Returns:** `void` + +**Example:** + +```groovy +extractArchive( + file("${buildTmpPath}/downloads/extension.zip"), + file("${buildTmpPath}/extracted") +) +``` + +**Supported Formats:** +- `.zip` - Extracted using Gradle's `zipTree()` +- `.7z` - Extracted using 7-Zip command line + +--- + +### `validateDllArchitecture(File dll)` + +**Description:** Validate DLL is 64-bit architecture + +**Parameters:** + +| Parameter | Type | Description | +|--------------|----------|--------------------------------------| +| `dll` | File | DLL file to validate | + +**Returns:** `void` + +**Throws:** `GradleException` if DLL is not 64-bit + +**Example:** + +```groovy +validateDllArchitecture(file('ext/php_redis.dll')) +``` + +**Process:** +1. Read DLL file bytes +2. Parse PE header +3. Check machine type (0x8664 = 64-bit) +4. Throw exception if not 64-bit + +--- + +### `processExtensions(File extsFile, File phpPrepPath)` + +**Description:** Process PHP extensions from configuration file + +**Parameters:** + +| Parameter | Type | Description | +|----------------|----------|--------------------------------------| +| `extsFile` | File | exts.properties file | +| `phpPrepPath` | File | PHP preparation directory | + +**Returns:** `void` + +**Example:** + +```groovy +def extsFile = new File(bundlePath, 'exts.properties') +if (extsFile.exists()) { + processExtensions(extsFile, phpPrepPath) +} +``` + +**Process:** +1. Load extensions from properties file +2. For each extension: + - Download extension archive/DLL + - Extract if needed + - Find main DLL file + - Validate 64-bit architecture + - Copy to ext/ directory + - Handle special cases (imagick) + - Add to php.ini (except xdebug) + +--- + +### `processPear(File pearFile, File phpPrepPath)` + +**Description:** Process PEAR installation from configuration file + +**Parameters:** + +| Parameter | Type | Description | +|----------------|----------|--------------------------------------| +| `pearFile` | File | pear.properties file | +| `phpPrepPath` | File | PHP preparation directory | + +**Returns:** `void` + +**Example:** + +```groovy +def pearFile = new File(bundlePath, 'pear.properties') +if (pearFile.exists()) { + processPear(pearFile, phpPrepPath) +} +``` + +**Process:** +1. Load PEAR configuration +2. Copy pear-install scripts +3. Download PEAR phar +4. Execute installation +5. Cleanup temporary files + +--- + +### `processDependencies(File depsFile, File phpPrepPath)` + +**Description:** Process dependencies from configuration file + +**Parameters:** + +| Parameter | Type | Description | +|----------------|----------|--------------------------------------| +| `depsFile` | File | deps.properties file | +| `phpPrepPath` | File | PHP preparation directory | + +**Returns:** `void` + +**Example:** + +```groovy +def depsFile = new File(bundlePath, 'deps.properties') +if (depsFile.exists()) { + processDependencies(depsFile, phpPrepPath) +} +``` + +**Process:** +1. Load dependencies from properties file +2. For each dependency: + - Download dependency archive + - Extract if needed + - Copy to appropriate directory + - Handle special cases (imagemagick) + +--- + +## Extension Points + +### Custom Task Registration + +Register custom tasks using Gradle's task API: + +```groovy +tasks.register('customTask') { + group = 'custom' + description = 'Custom task description' + + doLast { + // Task implementation + } +} +``` + +--- + +### Custom Extension Processing + +Override extension processing by defining custom function: + +```groovy +def customProcessExtensions(File extsFile, File phpPrepPath) { + // Custom implementation +} +``` + +--- + +### Custom Validation + +Add custom validation tasks: + +```groovy +tasks.register('customValidation') { + group = 'verification' + description = 'Custom validation' + + doLast { + // Validation logic + } +} +``` + +--- + +## Properties API + +### Project Properties + +Access via `project.findProperty()`: + +```groovy +def bundleVersion = project.findProperty('bundleVersion') +def dllFile = project.findProperty('dllFile') +``` + +--- + +### Build Properties + +Access via loaded Properties object: + +```groovy +def buildProps = new Properties() +file('build.properties').withInputStream { buildProps.load(it) } + +def bundleName = buildProps.getProperty('bundle.name') +def bundleRelease = buildProps.getProperty('bundle.release') +``` + +--- + +### System Properties + +Access via `System.getProperty()`: + +```groovy +def javaHome = System.getProperty('java.home') +def javaVersion = System.getProperty('java.version') +``` + +--- + +### Environment Variables + +Access via `System.getenv()`: + +```groovy +def gradleHome = System.getenv('GRADLE_HOME') +def path = System.getenv('PATH') +``` + +--- + +## Task API + +### Task Registration + +```groovy +tasks.register('taskName') { + group = 'groupName' + description = 'Task description' + + doFirst { + // Executed first + } + + doLast { + // Executed last + } +} +``` + +--- + +### Task Dependencies + +```groovy +tasks.register('taskB') { + dependsOn 'taskA' + + doLast { + // Executed after taskA + } +} +``` + +--- + +### Task Configuration + +```groovy +tasks.named('existingTask') { + // Configure existing task + doLast { + // Add additional action + } +} +``` + +--- + +### Task Execution + +```groovy +// Execute task programmatically +tasks.getByName('taskName').execute() + +// Execute task actions +tasks.getByName('taskName').actions.each { action -> + action.execute(tasks.getByName('taskName')) +} +``` + +--- + +## File API + +### File Operations + +```groovy +// Create file object +def file = file('path/to/file') + +// Check existence +if (file.exists()) { } + +// Create directory +file.mkdirs() + +// Delete file/directory +delete file + +// Copy files +copy { + from 'source' + into 'destination' + include '*.txt' + exclude '*.log' +} +``` + +--- + +### Archive Operations + +```groovy +// Extract ZIP +copy { + from zipTree('archive.zip') + into 'destination' +} + +// Extract 7z +exec { + executable '7z' + args 'x', 'archive.7z', '-odestination', '-y' +} +``` + +--- + +## Exec API + +### Execute Command + +```groovy +exec { + executable 'command' + args 'arg1', 'arg2' + workingDir file('directory') + standardOutput = new ByteArrayOutputStream() + ignoreExitValue = true +} +``` + +--- + +### PowerShell Execution + +```groovy +def output = new ByteArrayOutputStream() +exec { + executable 'powershell.exe' + args '-NoLogo', '-NoProfile', '-Command', 'Get-Date' + standardOutput = output +} +def result = output.toString().trim() +``` + +--- + +## Logger API + +### Logging Levels + +```groovy +logger.error('Error message') +logger.warn('Warning message') +logger.lifecycle('Lifecycle message') +logger.quiet('Quiet message') +logger.info('Info message') +logger.debug('Debug message') +``` + +--- + +### Conditional Logging + +```groovy +if (logger.isInfoEnabled()) { + logger.info('Info message') +} + +if (logger.isDebugEnabled()) { + logger.debug('Debug message') +} +``` + +--- + +## Exception API + +### Throw Exception + +```groovy +throw new GradleException('Error message') +``` + +--- + +### Custom Exception + +```groovy +class CustomException extends GradleException { + CustomException(String message) { + super(message) + } +} + +throw new CustomException('Custom error') +``` + +--- + +## Examples + +### Example 1: Custom Download Task + +```groovy +tasks.register('downloadPhp') { + group = 'download' + description = 'Download PHP version' + + doLast { + def version = project.findProperty('phpVersion') + if (!version) { + throw new GradleException('phpVersion property required') + } + + def url = "https://windows.php.net/downloads/releases/php-${version}-Win32-vs16-x64.zip" + def destDir = file("${buildTmpPath}/downloads") + + def archive = downloadFile(url, destDir) + println "Downloaded: ${archive}" + } +} +``` + +--- + +### Example 2: Custom Validation Task + +```groovy +tasks.register('validateExtensions') { + group = 'verification' + description = 'Validate all extension DLLs' + + doLast { + def extDir = file('ext') + if (!extDir.exists()) { + throw new GradleException('ext/ directory not found') + } + + extDir.listFiles().each { dll -> + if (dll.name.endsWith('.dll')) { + println "Validating: ${dll.name}" + validateDllArchitecture(dll) + } + } + + println '[SUCCESS] All extensions validated' + } +} +``` + +--- + +### Example 3: Custom Info Task + +```groovy +tasks.register('extensionInfo') { + group = 'help' + description = 'Display extension information' + + doLast { + def extDir = file('ext') + if (!extDir.exists()) { + println 'No extensions directory found' + return + } + + println 'Installed Extensions:' + println '-'.multiply(60) + + extDir.listFiles().each { dll -> + if (dll.name.endsWith('.dll')) { + def name = dll.name.replace('php_', '').replace('.dll', '') + def size = dll.length() / 1024 + println " ${name.padRight(20)} ${size} KB" + } + } + + println '-'.multiply(60) + } +} +``` + +--- + +**Last Updated**: 2025-01-31 +**Version**: 2025.10.31 diff --git a/.gradle-docs/CONFIGURATION.md b/.gradle-docs/CONFIGURATION.md new file mode 100644 index 00000000..3b035c37 --- /dev/null +++ b/.gradle-docs/CONFIGURATION.md @@ -0,0 +1,417 @@ +# Configuration Guide + +Complete configuration guide for the Bearsampp Module PHP Gradle build system. + +--- + +## Table of Contents + +- [Configuration Files](#configuration-files) +- [Build Properties](#build-properties) +- [Gradle Properties](#gradle-properties) +- [PHP Version Configuration](#php-version-configuration) +- [Extension Configuration](#extension-configuration) +- [PEAR Configuration](#pear-configuration) +- [Dependency Configuration](#dependency-configuration) +- [Environment Variables](#environment-variables) + +--- + +## Configuration Files + +### Overview + +| File | Purpose | Format | Location | +|-----------------------|------------------------------------------|------------|---------------| +| `build.properties` | Main build configuration | Properties | Root | +| `gradle.properties` | Gradle-specific settings | Properties | Root | +| `settings.gradle` | Gradle project settings | Groovy | Root | +| `build.gradle` | Main build script | Groovy | Root | +| `releases.properties` | Available PHP releases | Properties | Root | +| `exts.properties` | PHP extensions per version | Properties | bin/php{ver}/ | +| `pear.properties` | PEAR configuration per version | Properties | bin/php{ver}/ | +| `deps.properties` | Dependencies per version | Properties | bin/php{ver}/ | + +--- + +## Build Properties + +### File: `build.properties` + +**Location:** `E:/Bearsampp-development/module-php/build.properties` + +**Purpose:** Main build configuration for the PHP module + +### Properties + +| Property | Type | Required | Default | Description | +|-------------------|----------|----------|--------------|--------------------------------------| +| `bundle.name` | String | Yes | `php` | Name of the bundle | +| `bundle.release` | String | Yes | - | Release version (YYYY.MM.DD) | +| `bundle.type` | String | Yes | `bins` | Type of bundle | +| `bundle.format` | String | Yes | `7z` | Archive format for output | + +### Example + +```properties +bundle.name = php +bundle.release = 2025.10.31 +bundle.type = bins +bundle.format = 7z +``` + +### Usage in Build Script + +```groovy +def buildProps = new Properties() +file('build.properties').withInputStream { buildProps.load(it) } + +def bundleName = buildProps.getProperty('bundle.name', 'php') +def bundleRelease = buildProps.getProperty('bundle.release', '1.0.0') +``` + +--- + +## Gradle Properties + +### File: `gradle.properties` + +**Location:** `E:/Bearsampp-development/module-php/gradle.properties` + +**Purpose:** Gradle-specific configuration and JVM settings + +### Properties + +| Property | Type | Default | Description | +|-------------------------------|----------|--------------|--------------------------------------| +| `org.gradle.daemon` | Boolean | `true` | Enable Gradle daemon | +| `org.gradle.parallel` | Boolean | `true` | Enable parallel task execution | +| `org.gradle.caching` | Boolean | `true` | Enable build caching | +| `org.gradle.jvmargs` | String | - | JVM arguments for Gradle | +| `org.gradle.configureondemand`| Boolean | `false` | Configure projects on demand | + +### Example + +```properties +# Gradle daemon configuration +org.gradle.daemon=true +org.gradle.parallel=true +org.gradle.caching=true + +# JVM settings +org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError + +# Configure on demand +org.gradle.configureondemand=false +``` + +### Performance Tuning + +| Setting | Recommended Value | Purpose | +|-------------------------------|-------------------|--------------------------------------| +| `org.gradle.daemon` | `true` | Faster builds with daemon | +| `org.gradle.parallel` | `true` | Parallel task execution | +| `org.gradle.caching` | `true` | Cache task outputs | +| `org.gradle.jvmargs` | `-Xmx2g` | Allocate 2GB heap for Gradle | + +--- + +## PHP Version Configuration + +### Directory Structure + +Each PHP version has its own directory in `bin/`: + +``` +bin/ +├── php8.1.32/ +│ ├── exts.properties +│ ├── pear.properties +│ ├── deps.properties +│ └── php.ini +├── php8.3.15/ +│ ├── exts.properties +│ ├── pear.properties +│ ├── deps.properties +│ └── php.ini +└── php8.4.14/ + ├── exts.properties + ├── pear.properties + ├── deps.properties + └── php.ini +``` + +### Version Naming Convention + +| Pattern | Example | Description | +|-------------------|---------------|--------------------------------------| +| `php{major}.{minor}.{patch}` | `php8.3.15` | Standard PHP version format | + +--- + +## Extension Configuration + +### File: `exts.properties` + +**Location:** `bin/php{version}/exts.properties` + +**Purpose:** Define PHP extensions to include in the build + +### Format + +```properties +phpexts.{extension_name}={download_url} +``` + +### Example + +```properties +phpexts.imagick=https://windows.php.net/downloads/pecl/releases/imagick/3.7.0/php_imagick-3.7.0-8.3-ts-vs16-x64.zip +phpexts.redis=https://windows.php.net/downloads/pecl/releases/redis/6.0.2/php_redis-6.0.2-8.3-ts-vs16-x64.zip +phpexts.xdebug=https://xdebug.org/files/php_xdebug-3.3.1-8.3-vs16-x86_64.dll +phpexts.opcache=https://windows.php.net/downloads/pecl/releases/opcache/8.3.15/php_opcache-8.3.15-ts-vs16-x64.zip +``` + +### Extension Properties + +| Property | Type | Required | Description | +|---------------------------|----------|----------|--------------------------------------| +| `phpexts.{name}` | URL | Yes | Download URL for extension | + +### Supported Extension Formats + +| Format | Description | Example | +|-----------|------------------------------------------|--------------------------------------| +| `.zip` | ZIP archive containing DLL | `php_redis-6.0.2-8.3-ts-vs16-x64.zip` | +| `.dll` | Direct DLL file | `php_xdebug-3.3.1-8.3-vs16-x86_64.dll` | + +### Extension Processing + +1. **Download:** Extension is downloaded from the specified URL +2. **Extract:** If ZIP, extract to temporary directory +3. **Validate:** Check DLL is 64-bit architecture +4. **Copy:** Copy `php_{name}.dll` to `ext/` directory +5. **Configure:** Add `extension={name}` to `php.ini` (except xdebug) + +### Special Extensions + +| Extension | Special Handling | +|---------------|------------------------------------------| +| `imagick` | Copies `CORE_*.dll` files to `imagick/` directory | +| `xdebug` | Not added to `extension=` list in php.ini | + +--- + +## PEAR Configuration + +### File: `pear.properties` + +**Location:** `bin/php{version}/pear.properties` + +**Purpose:** Configure PEAR installation for the PHP version + +### Format + +```properties +phppear.pear={pear_phar_url} +``` + +### Example + +```properties +phppear.pear=https://pear.php.net/install-pear-nozlib.phar +``` + +### PEAR Properties + +| Property | Type | Required | Description | +|-------------------|----------|----------|--------------------------------------| +| `phppear.pear` | URL | Yes | URL to PEAR installation phar | + +### PEAR Processing + +1. **Copy Scripts:** Copy `pear-install.bat` and `pear-install.php` to temp directory +2. **Download:** Download PEAR phar from specified URL +3. **Rename:** Rename to `install-pear-nozlib.phar` +4. **Execute:** Run `pear-install.bat` to install PEAR +5. **Cleanup:** Remove temporary installation files + +--- + +## Dependency Configuration + +### File: `deps.properties` + +**Location:** `bin/php{version}/deps.properties` + +**Purpose:** Define external dependencies required by PHP extensions + +### Format + +```properties +phpdeps.{dependency_name}={download_url} +``` + +### Example + +```properties +phpdeps.imagemagick=https://imagemagick.org/archive/binaries/ImageMagick-7.1.1-28-portable-Q16-HDRI-x64.zip +``` + +### Dependency Properties + +| Property | Type | Required | Description | +|-----------------------|----------|----------|--------------------------------------| +| `phpdeps.{name}` | URL | Yes | Download URL for dependency | + +### Supported Dependencies + +| Dependency | Purpose | Target Directory | +|-------------------|--------------------------------------|------------------| +| `imagemagick` | Image processing for imagick ext | `imagick/` | + +### Dependency Processing + +1. **Download:** Dependency is downloaded from the specified URL +2. **Extract:** If archive, extract to temporary directory +3. **Copy:** Copy required files to target directory +4. **Validate:** Ensure all required files are present + +--- + +## Environment Variables + +### Build Environment + +| Variable | Description | Example | +|-------------------|--------------------------------------|--------------------------------------| +| `JAVA_HOME` | Java installation directory | `C:\Program Files\Java\jdk-11` | +| `GRADLE_HOME` | Gradle installation directory | `C:\Gradle\gradle-8.5` | +| `PATH` | System path (includes Java, Gradle) | - | + +### Optional Variables + +| Variable | Description | Default | +|-------------------|--------------------------------------|--------------------------------------| +| `GRADLE_USER_HOME`| Gradle user home directory | `~/.gradle` | +| `GRADLE_OPTS` | Additional Gradle JVM options | - | + +--- + +## Configuration Examples + +### Example 1: Basic PHP 8.3.15 Configuration + +**build.properties:** +```properties +bundle.name = php +bundle.release = 2025.10.31 +bundle.type = bins +bundle.format = 7z +``` + +**bin/php8.3.15/exts.properties:** +```properties +phpexts.redis=https://windows.php.net/downloads/pecl/releases/redis/6.0.2/php_redis-6.0.2-8.3-ts-vs16-x64.zip +phpexts.xdebug=https://xdebug.org/files/php_xdebug-3.3.1-8.3-vs16-x86_64.dll +``` + +**bin/php8.3.15/pear.properties:** +```properties +phppear.pear=https://pear.php.net/install-pear-nozlib.phar +``` + +--- + +### Example 2: PHP 8.4.14 with ImageMagick + +**bin/php8.4.14/exts.properties:** +```properties +phpexts.imagick=https://windows.php.net/downloads/pecl/releases/imagick/3.7.0/php_imagick-3.7.0-8.4-ts-vs16-x64.zip +phpexts.redis=https://windows.php.net/downloads/pecl/releases/redis/6.0.2/php_redis-6.0.2-8.4-ts-vs16-x64.zip +``` + +**bin/php8.4.14/deps.properties:** +```properties +phpdeps.imagemagick=https://imagemagick.org/archive/binaries/ImageMagick-7.1.1-28-portable-Q16-HDRI-x64.zip +``` + +--- + +### Example 3: Minimal Configuration (No Extensions) + +**bin/php8.1.32/exts.properties:** +```properties +# No extensions configured +``` + +**bin/php8.1.32/pear.properties:** +```properties +phppear.pear=https://pear.php.net/install-pear-nozlib.phar +``` + +--- + +## Configuration Validation + +### Validate Configuration + +```bash +# Validate build.properties +gradle validateProperties + +# Verify entire environment +gradle verify + +# List configured extensions +gradle listExtensions + +# List configured PEAR +gradle listPearConfig + +# List configured dependencies +gradle listDependencies +``` + +### Validation Checklist + +| Item | Command | Expected Result | +|---------------------------|----------------------------------|------------------------------| +| Build properties | `gradle validateProperties` | All required properties set | +| Environment | `gradle verify` | All checks pass | +| Extensions | `gradle listExtensions` | Extensions listed | +| PEAR | `gradle listPearConfig` | PEAR config listed | +| Dependencies | `gradle listDependencies` | Dependencies listed | + +--- + +## Best Practices + +### Configuration Management + +1. **Version Control:** Keep all `.properties` files in version control +2. **Documentation:** Document custom configurations +3. **Validation:** Always run `gradle verify` after configuration changes +4. **Testing:** Test builds with new configurations before committing +5. **Backup:** Keep backups of working configurations + +### Extension Management + +1. **64-bit Only:** Always use 64-bit extensions +2. **Version Matching:** Match extension version to PHP version +3. **Thread Safety:** Use thread-safe (TS) extensions +4. **Compiler:** Match Visual Studio compiler version (vs16) +5. **Testing:** Test extensions after adding to configuration + +### Performance Optimization + +1. **Gradle Daemon:** Enable for faster builds +2. **Parallel Execution:** Enable for multi-core systems +3. **Build Cache:** Enable for incremental builds +4. **JVM Heap:** Allocate sufficient memory (2GB+) +5. **Network:** Use fast, reliable network for downloads + +--- + +**Last Updated**: 2025-01-31 +**Version**: 2025.10.31 diff --git a/.gradle-docs/INDEX.md b/.gradle-docs/INDEX.md new file mode 100644 index 00000000..f1578295 --- /dev/null +++ b/.gradle-docs/INDEX.md @@ -0,0 +1,349 @@ +# Documentation Index + +Complete index of all Gradle build documentation for Bearsampp Module PHP. + +--- + +## Quick Links + +| Document | Description | Link | +|-----------------------|--------------------------------------------------|-------------------------------| +| **Main Documentation**| Complete build system guide | [README.md](README.md) | +| **Task Reference** | All available Gradle tasks | [TASKS.md](TASKS.md) | +| **Configuration** | Configuration files and properties | [CONFIGURATION.md](CONFIGURATION.md) | +| **API Reference** | Build script API and helper functions | [API.md](API.md) | + +--- + +## Documentation Structure + +``` +.gradle-docs/ +├── INDEX.md # This file - Documentation index +├── README.md # Main documentation and quick start +├── TASKS.md # Complete task reference +├── CONFIGURATION.md # Configuration guide +└── API.md # API reference for build scripts +``` + +--- + +## Getting Started + +### New Users + +1. **Start Here**: [README.md](README.md) - Overview and quick start +2. **Verify Setup**: Run `gradle verify` to check environment +3. **List Tasks**: Run `gradle tasks` to see available tasks +4. **Build Release**: Run `gradle release -PbundleVersion=8.3.15` + +### Advanced Users + +1. **Task Reference**: [TASKS.md](TASKS.md) - All tasks with examples +2. **Configuration**: [CONFIGURATION.md](CONFIGURATION.md) - Advanced configuration +3. **API Reference**: [API.md](API.md) - Build script API and extensions +4. **Custom Tasks**: Create custom tasks using API reference + +--- + +## Documentation by Topic + +### Build System + +| Topic | Document | Section | +|-----------------------|-----------------------|----------------------------------| +| Overview | README.md | Overview | +| Quick Start | README.md | Quick Start | +| Installation | README.md | Installation | +| Architecture | README.md | Architecture | + +### Tasks + +| Topic | Document | Section | +|-----------------------|-----------------------|----------------------------------| +| Build Tasks | TASKS.md | Build Tasks | +| Verification Tasks | TASKS.md | Verification Tasks | +| Information Tasks | TASKS.md | Information Tasks | +| Task Examples | TASKS.md | Task Examples | + +### Configuration + +| Topic | Document | Section | +|-----------------------|-----------------------|----------------------------------| +| Build Properties | CONFIGURATION.md | Build Properties | +| Gradle Properties | CONFIGURATION.md | Gradle Properties | +| PHP Versions | CONFIGURATION.md | PHP Version Configuration | +| Extensions | CONFIGURATION.md | Extension Configuration | +| PEAR | CONFIGURATION.md | PEAR Configuration | +| Dependencies | CONFIGURATION.md | Dependency Configuration | + +### API + +| Topic | Document | Section | +|-----------------------|-----------------------|----------------------------------| +| Build Script API | API.md | Build Script API | +| Helper Functions | API.md | Helper Functions | +| Extension Points | API.md | Extension Points | +| Properties API | API.md | Properties API | +| Task API | API.md | Task API | + +--- + +## Common Tasks + +### Building + +| Task | Document | Reference | +|-------------------------------------------|---------------|----------------------------------| +| Build a release | README.md | Quick Start | +| Build specific version | TASKS.md | release task | +| Clean build artifacts | TASKS.md | clean task | + +### Configuration + +| Task | Document | Reference | +|-------------------------------------------|---------------|----------------------------------| +| Configure build properties | CONFIGURATION.md | Build Properties | +| Configure extensions | CONFIGURATION.md | Extension Configuration | +| Configure PEAR | CONFIGURATION.md | PEAR Configuration | +| Configure dependencies | CONFIGURATION.md | Dependency Configuration | + +### Verification + +| Task | Document | Reference | +|-------------------------------------------|---------------|----------------------------------| +| Verify build environment | TASKS.md | verify task | +| Validate properties | TASKS.md | validateProperties task | +| Validate DLL architecture | TASKS.md | validateDllArchitecture task | + +### Information + +| Task | Document | Reference | +|-------------------------------------------|---------------|----------------------------------| +| Display build info | TASKS.md | info task | +| List available versions | TASKS.md | listVersions task | +| List extensions | TASKS.md | listExtensions task | +| List PEAR config | TASKS.md | listPearConfig task | + +--- + +## Quick Reference + +### Essential Commands + +```bash +# Display build information +gradle info + +# List all available tasks +gradle tasks + +# Verify build environment +gradle verify + +# Build a release (interactive) +gradle release + +# Build a specific version (non-interactive) +gradle release -PbundleVersion=8.3.15 + +# Clean build artifacts +gradle clean +``` + +### Essential Files + +| File | Purpose | +|-----------------------|------------------------------------------| +| `build.gradle` | Main Gradle build script | +| `settings.gradle` | Gradle project settings | +| `build.properties` | Build configuration | +| `gradle.properties` | Gradle-specific settings | +| `releases.properties` | Available PHP releases | + +### Essential Directories + +| Directory | Purpose | +|-----------------------|------------------------------------------| +| `bin/` | PHP version bundles | +| `pear/` | PEAR installation scripts | +| `bearsampp-build/tmp/` | Temporary build files (external) | +| `bearsampp-build/bins/` | Final packaged archives (external) | +| `.gradle-docs/` | Gradle documentation | + +--- + +## Search by Keyword + +### A-C + +| Keyword | Document | Section | +|-----------------------|-----------------------|----------------------------------| +| API | API.md | All sections | +| Architecture | README.md | Architecture | +| Build | TASKS.md | Build Tasks | +| Clean | TASKS.md | clean task | +| Configuration | CONFIGURATION.md | All sections | + +### D-F + +| Keyword | Document | Section | +|-----------------------|-----------------------|----------------------------------| +| Dependencies | CONFIGURATION.md | Dependency Configuration | +| DLL | TASKS.md | validateDllArchitecture task | +| Extensions | CONFIGURATION.md | Extension Configuration | +| Files | CONFIGURATION.md | Configuration Files | + +### G-M + +| Keyword | Document | Section | +|-----------------------|-----------------------|----------------------------------| +| Gradle | README.md | All sections | +| Helper Functions | API.md | Helper Functions | +| Info | TASKS.md | info task | +| Installation | README.md | Installation | + +### P-R + +| Keyword | Document | Section | +|-----------------------|-----------------------|----------------------------------| +| PEAR | CONFIGURATION.md | PEAR Configuration | +| Properties | CONFIGURATION.md | Build Properties | +| Release | TASKS.md | release task | + +### S-Z + +| Keyword | Document | Section | +|-----------------------|-----------------------|----------------------------------| +| Tasks | TASKS.md | All sections | +| Troubleshooting | README.md | Troubleshooting | +| Validation | TASKS.md | Verification Tasks | +| Verify | TASKS.md | verify task | + +--- + +## Document Summaries + +### README.md + +**Purpose**: Main documentation and quick start guide + +**Contents**: +- Overview of the Gradle build system +- Quick start guide with basic commands +- Installation instructions +- Complete task reference +- Configuration overview +- Architecture and build process flow +- Troubleshooting common issues +- Migration guide summary + +**Target Audience**: All users, especially new users + +--- + +### TASKS.md + +**Purpose**: Complete reference for all Gradle tasks + +**Contents**: +- Build tasks (release, releaseBuild, clean) +- Verification tasks (verify, validateProperties, validateDllArchitecture) +- Information tasks (info, phpInfo, listVersions, etc.) +- Task dependencies and execution order +- Task examples and usage patterns +- Task options and properties + +**Target Audience**: Developers and build engineers + +--- + +### CONFIGURATION.md + +**Purpose**: Configuration guide for build system + +**Contents**: +- Configuration files overview +- Build properties reference +- Gradle properties reference +- PHP version configuration +- Extension configuration +- PEAR configuration +- Dependency configuration +- Environment variables +- Configuration examples +- Best practices + +**Target Audience**: Build engineers and advanced users + +--- + +### API.md + +**Purpose**: API reference for build scripts + +**Contents**: +- Build script API +- Helper functions reference +- Extension points +- Properties API +- Task API +- File operations API +- Exec API +- Logger API +- Exception handling +- API examples + +**Target Audience**: Advanced users and contributors + +--- + +--- + +## Version History + +| Version | Date | Changes | +|---------------|------------|------------------------------------------| +| 2025.10.31 | 2025-01-31 | Initial Gradle documentation | +| | | - Created README.md | +| | | - Created TASKS.md | +| | | - Created CONFIGURATION.md | +| | | - Created API.md | +| | | - Created INDEX.md | + +--- + +## Contributing + +To contribute to the documentation: + +1. **Fork Repository**: Fork the module-php repository +2. **Edit Documentation**: Make changes to documentation files +3. **Follow Style**: Maintain consistent formatting and style +4. **Test Examples**: Verify all code examples work +5. **Submit PR**: Create pull request with changes + +### Documentation Style Guide + +- Use Markdown formatting +- Include code examples +- Use tables for structured data +- Add links to related sections +- Keep language clear and concise +- Include practical examples + +--- + +## Support + +For documentation issues or questions: + +- **GitHub Issues**: https://github.com/bearsampp/module-php/issues +- **Bearsampp Issues**: https://github.com/bearsampp/bearsampp/issues +- **Documentation**: This directory (.gradle-docs/) + +--- + +**Last Updated**: 2025-01-31 +**Version**: 2025.10.31 +**Total Documents**: 5 diff --git a/.gradle-docs/README.md b/.gradle-docs/README.md new file mode 100644 index 00000000..6bf4417f --- /dev/null +++ b/.gradle-docs/README.md @@ -0,0 +1,479 @@ +# Bearsampp Module PHP - Gradle Build Documentation + +## Table of Contents + +- [Overview](#overview) +- [Quick Start](#quick-start) +- [Installation](#installation) +- [Build Tasks](#build-tasks) +- [Configuration](#configuration) +- [Architecture](#architecture) +- [Troubleshooting](#troubleshooting) + +--- + +## Overview + +The Bearsampp Module PHP 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 +- **Better Performance** - Incremental builds and caching +- **Simplified Maintenance** - Pure Groovy/Gradle DSL +- **Enhanced Tooling** - IDE integration and dependency management +- **Cross-Platform Support** - Works on Windows, Linux, and macOS + +### Project Information + +| Property | Value | +|-------------------|------------------------------------------| +| **Project Name** | module-php | +| **Group** | com.bearsampp.modules | +| **Type** | PHP Module Builder | +| **Build Tool** | Gradle 8.x+ | +| **Language** | Groovy (Gradle DSL) | + +--- + +## Quick Start + +### Prerequisites + +| Requirement | Version | Purpose | +|-------------------|---------------|------------------------------------------| +| **Java** | 8+ | Required for Gradle execution | +| **Gradle** | 8.0+ | Build automation tool | +| **PowerShell** | 5.0+ | DLL architecture validation | +| **7-Zip** | Latest | Archive extraction (optional) | + +### Basic Commands + +```bash +# Display build information +gradle info + +# List all available tasks +gradle tasks + +# Verify build environment +gradle verify + +# Build a release (interactive) +gradle release + +# Build a specific version (non-interactive) +gradle release -PbundleVersion=8.3.15 + +# Clean build artifacts +gradle clean +``` + +--- + +## Installation + +### 1. Clone the Repository + +```bash +git clone https://github.com/bearsampp/module-php.git +cd module-php +``` + +### 2. Verify Environment + +```bash +gradle verify +``` + +This will check: +- Java version (8+) +- Required files (build.properties, releases.properties) +- Directory structure (bin/, pear/) +- Build dependencies + +### 3. List Available Versions + +```bash +gradle listVersions +``` + +### 4. Build Your First Release + +```bash +# Interactive mode (prompts for version) +gradle release + +# Or specify version directly +gradle release -PbundleVersion=8.4.14 +``` + +--- + +## Build Tasks + +### Core Build Tasks + +| Task | Description | Example | +|-----------------------|--------------------------------------------------|------------------------------------------| +| `release` | Build and package release (interactive/non-interactive) | `gradle release -PbundleVersion=8.3.15` | +| `releaseBuild` | Execute the release build process | `gradle releaseBuild` | +| `clean` | Clean build artifacts and temporary files | `gradle clean` | + +### Verification Tasks + +| Task | Description | Example | +|---------------------------|----------------------------------------------|----------------------------------------------| +| `verify` | Verify build environment and dependencies | `gradle verify` | +| `validateProperties` | Validate build.properties configuration | `gradle validateProperties` | +| `validateDllArchitecture` | Validate DLL architecture (64-bit check) | `gradle validateDllArchitecture -PdllFile=` | + +### Information Tasks + +| Task | Description | Example | +|---------------------|--------------------------------------------------|----------------------------| +| `info` | Display build configuration information | `gradle info` | +| `phpInfo` | Display PHP-specific build information | `gradle phpInfo` | +| `listVersions` | List available bundle versions in bin/ | `gradle listVersions` | +| `listReleases` | List all available releases from releases.properties | `gradle listReleases` | +| `listExtensions` | List PHP extensions configured in bin/ | `gradle listExtensions` | +| `listPearConfig` | List PEAR configurations in bin/ | `gradle listPearConfig` | +| `listDependencies` | List dependencies configured in bin/ | `gradle listDependencies` | + +### Task Groups + +| Group | Purpose | +|------------------|--------------------------------------------------| +| **build** | Build and package tasks | +| **verification** | Verification and validation tasks | +| **help** | Help and information tasks | + +--- + +## Configuration + +### build.properties + +The main configuration file for the build: + +```properties +bundle.name = php +bundle.release = 2025.10.31 +bundle.type = bins +bundle.format = 7z +``` + +| Property | Description | Example Value | +|-------------------|--------------------------------------|----------------| +| `bundle.name` | Name of the bundle | `php` | +| `bundle.release` | Release version | `2025.10.31` | +| `bundle.type` | Type of bundle | `bins` | +| `bundle.format` | Archive format | `7z` | + +### gradle.properties + +Gradle-specific configuration: + +```properties +# Gradle daemon configuration +org.gradle.daemon=true +org.gradle.parallel=true +org.gradle.caching=true + +# JVM settings +org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m +``` + +### Directory Structure + +``` +module-php/ +├── .gradle-docs/ # Gradle documentation +│ ├── README.md # Main documentation +│ ├── TASKS.md # Task reference +│ ├── CONFIGURATION.md # Configuration guide +│ └── API.md # API reference +├── bin/ # PHP version bundles +│ ├── php8.3.15/ +│ ├── php8.4.14/ +│ └── ... +├── pear/ # PEAR installation scripts +│ ├── pear-install.bat +│ └── pear-install.php +bearsampp-build/ # External build directory (outside repo) +├── tmp/ # Temporary build files +│ ├── bundles_prep/bins/php/ # Prepared bundles +│ ├── bundles_build/bins/php/ # Build staging +│ ├── downloads/php/ # Downloaded dependencies +│ └── extract/php/ # Extracted archives +└── bins/php/ # Final packaged archives + └── 2025.10.31/ # Release version + ├── bearsampp-php-8.3.15-2025.10.31.7z + ├── bearsampp-php-8.3.15-2025.10.31.7z.md5 + └── ... +├── build.gradle # Main Gradle build script +├── settings.gradle # Gradle settings +├── build.properties # Build configuration +└── releases.properties # Available PHP releases +``` + +--- + +## Architecture + +### Build Process Flow + +``` +1. User runs: gradle release -PbundleVersion=8.3.15 + ↓ +2. Validate environment and version + ↓ +3. Create preparation directory (tmp/prep/) + ↓ +4. Copy base PHP files from bin/php8.3.15/ + ↓ +5. Process extensions (if exts.properties exists) + - Download extensions + - Validate 64-bit architecture + - Copy to ext/ directory + - Update php.ini + ↓ +6. Process PEAR (if pear.properties exists) + - Download PEAR phar + - Execute installation + ↓ +7. Process dependencies (if deps.properties exists) + - Download dependencies (e.g., ImageMagick) + - Copy to appropriate directories + ↓ +8. Output prepared bundle to tmp/prep/ + ↓ +9. Package prepared folder into archive in bearsampp-build/bins/php/{bundle.release}/ + - The archive includes the top-level folder: php{version}/ +``` + +### Packaging Details + +- **Archive name format**: `bearsampp-php-{version}-{bundle.release}.{7z|zip}` +- **Location**: `bearsampp-build/bins/php/{bundle.release}/` + - Example: `bearsampp-build/bins/php/2025.10.31/bearsampp-php-8.3.15-2025.10.31.7z` +- **Content root**: The top-level folder inside the archive is `php{version}/` (e.g., `php8.3.15/`) +- **Structure**: The archive contains the PHP version folder at the root with all PHP files inside + +**Archive Structure Example**: +``` +bearsampp-php-8.3.15-2025.10.31.7z +└── php8.3.15/ ← Version folder at root + ├── php.exe + ├── php-cgi.exe + ├── php.ini + ├── ext/ ← Extensions directory + │ ├── php_curl.dll + │ ├── php_mbstring.dll + │ └── ... + ├── pear/ ← PEAR installation (if configured) + └── ... +``` + +**Verification Commands**: + +```bash +# List archive contents with 7z +7z l bearsampp-build/bins/php/2025.10.31/bearsampp-php-8.3.15-2025.10.31.7z | more + +# You should see entries beginning with: +# php8.3.15/php.exe +# php8.3.15/php-cgi.exe +# php8.3.15/ext/php_curl.dll +# php8.3.15/... + +# Extract and inspect with PowerShell (zip example) +Expand-Archive -Path bearsampp-build/bins/php/2025.10.31/bearsampp-php-8.3.15-2025.10.31.zip -DestinationPath .\_inspect +Get-ChildItem .\_inspect\php8.3.15 | Select-Object Name + +# Expected output: +# php.exe +# php-cgi.exe +# php.ini +# ext/ +# ... +``` + +**Note**: This archive structure matches the MySQL module pattern where archives contain `mysql{version}/` at the root. This ensures consistency across all Bearsampp modules. + +**Hash Files**: Each archive is accompanied by hash sidecar files: +- `.md5` - MD5 checksum +- `.sha1` - SHA-1 checksum +- `.sha256` - SHA-256 checksum +- `.sha512` - SHA-512 checksum + +Example: +``` +bearsampp-build/bins/php/2025.10.31/ +├── bearsampp-php-8.3.15-2025.10.31.7z +├── bearsampp-php-8.3.15-2025.10.31.7z.md5 +├── bearsampp-php-8.3.15-2025.10.31.7z.sha1 +├── bearsampp-php-8.3.15-2025.10.31.7z.sha256 +└── bearsampp-php-8.3.15-2025.10.31.7z.sha512 +``` +### Extension Processing + +Each PHP version can have an `exts.properties` file: + +```properties +phpexts.imagick=https://windows.php.net/downloads/pecl/releases/imagick/3.7.0/php_imagick-3.7.0-8.3-ts-vs16-x64.zip +phpexts.redis=https://windows.php.net/downloads/pecl/releases/redis/6.0.2/php_redis-6.0.2-8.3-ts-vs16-x64.zip +phpexts.xdebug=https://xdebug.org/files/php_xdebug-3.3.1-8.3-vs16-x86_64.dll +``` + +### PEAR Processing + +PEAR configuration in `pear.properties`: + +```properties +phppear.pear=https://pear.php.net/install-pear-nozlib.phar +``` + +### Dependency Processing + +Dependencies in `deps.properties`: + +```properties +phpdeps.imagemagick=https://imagemagick.org/archive/binaries/ImageMagick-7.1.1-28-portable-Q16-HDRI-x64.zip +``` + +--- + +## Troubleshooting + +### Common Issues + +#### Issue: "Dev path not found" + +**Symptom:** +``` +Dev path not found: E:/Bearsampp-development/dev +``` + +**Solution:** +This is a warning only. The dev path is optional for most tasks. If you need it, ensure the `dev` project exists in the parent directory. + +--- + +#### Issue: "Bundle version not found" + +**Symptom:** +``` +Bundle version not found: E:/Bearsampp-development/module-php/bin/php8.3.99 +``` + +**Solution:** +1. List available versions: `gradle listVersions` +2. Use an existing version: `gradle release -PbundleVersion=8.3.15` + +--- + +#### Issue: "DLL is not 64-bit architecture" + +**Symptom:** +``` +DLL php_extension.dll is not 64-bit architecture. Build halted. +``` + +**Solution:** +The extension DLL is not 64-bit compatible. Download the correct 64-bit version from the extension provider. + +--- + +#### Issue: "Java version too old" + +**Symptom:** +``` +Java 8+ required +``` + +**Solution:** +1. Check Java version: `java -version` +2. Install Java 8 or higher +3. Update JAVA_HOME environment variable + +--- + +### Debug Mode + +Run Gradle with debug output: + +```bash +gradle release -PbundleVersion=8.3.15 --info +gradle release -PbundleVersion=8.3.15 --debug +``` + +### Clean Build + +If you encounter issues, try a clean build: + +```bash +gradle clean +gradle release -PbundleVersion=8.3.15 +``` + +--- + +## Migration Guide + +### From Ant to Gradle + +The project has been fully migrated from Ant to Gradle. Here's what changed: + +#### Removed Files + +| File | Status | Replacement | +|-------------------|-----------|----------------------------| +| `build.xml` | ❌ Removed | `build.gradle` | +| `sigcheck.xml` | ❌ Removed | Integrated into build.gradle | + +#### Command Mapping + +| Ant Command | Gradle Command | +|--------------------------------------|---------------------------------------------| +| `ant release` | `gradle release` | +| `ant release -Dinput.bundle=8.3.15` | `gradle release -PbundleVersion=8.3.15` | +| `ant clean` | `gradle clean` | + +#### Key Differences + +| Aspect | Ant | Gradle | +|---------------------|------------------------------|----------------------------------| +| **Build File** | XML (build.xml) | Groovy DSL (build.gradle) | +| **Task Definition** | `` | `tasks.register('...')` | +| **Properties** | `` | `ext { ... }` | +| **Dependencies** | Manual downloads | Automatic with repositories | +| **Caching** | None | Built-in incremental builds | +| **IDE Support** | Limited | Excellent (IntelliJ, Eclipse) | + +--- + +## Additional Resources + +- [Gradle Documentation](https://docs.gradle.org/) +- [Bearsampp Project](https://github.com/bearsampp/bearsampp) +- [PHP Downloads](https://windows.php.net/download/) +- [PECL Extensions](https://windows.php.net/downloads/pecl/) + +--- + +## Support + +For issues and questions: + +- **GitHub Issues**: https://github.com/bearsampp/module-php/issues +- **Bearsampp Issues**: https://github.com/bearsampp/bearsampp/issues +- **Documentation**: https://bearsampp.com/module/php + +--- + +**Last Updated**: 2025-01-31 +**Version**: 2025.10.31 +**Build System**: Pure Gradle (no wrapper, no Ant) + +Notes: +- This project deliberately does not ship the Gradle Wrapper. Install Gradle 8+ locally and run with `gradle ...`. +- Legacy Ant files (e.g., Eclipse `.launch` referencing `build.xml`) are deprecated and not used by the build. diff --git a/.gradle-docs/TASKS.md b/.gradle-docs/TASKS.md new file mode 100644 index 00000000..11fb9f24 --- /dev/null +++ b/.gradle-docs/TASKS.md @@ -0,0 +1,558 @@ +# Gradle Tasks Reference + +Complete reference for all available Gradle tasks in the Bearsampp Module PHP project. + +--- + +## Table of Contents + +- [Build Tasks](#build-tasks) +- [Verification Tasks](#verification-tasks) +- [Information Tasks](#information-tasks) +- [Task Dependencies](#task-dependencies) +- [Task Examples](#task-examples) + +--- + +## Build Tasks + +### `release` + +**Group:** build +**Description:** Build release package. Interactive by default; non-interactive when `-PbundleVersion=*` (latest) or a concrete version is provided. + +**Usage:** +```bash +# Interactive mode (default — prompts for version) +gradle release + +# Non-interactive (build latest found in bin/) +gradle release -PbundleVersion=* + +# Non-interactive (build specific version) +gradle release -PbundleVersion=8.4.14 +``` + +**Parameters:** + +| Parameter | Type | Required | Description | Example | +|-------------------|----------|----------|--------------------------------|--------------| +| `bundleVersion` | String | No | PHP version to build | `8.3.15` | + +**Process:** +1. Validates environment and version +2. Creates preparation directory +3. Copies base PHP files +4. Processes extensions (if configured) +5. Processes PEAR (if configured) +6. Processes dependencies (if configured) +7. Outputs prepared bundle under `bearsampp-build/tmp/bundles_prep/bins/php/php{version}/` +8. Packages the prepared folder into an archive in `bearsampp-build/bins/php/{bundle.release}/` ensuring the top-level folder inside the archive is `{bundle.release}/` containing `bin/` and `bin/archived/` + +**Output Locations:** +- Prepared folder: `bearsampp-build/tmp/bundles_prep/bins/php/php{version}/` +- Final archive: `bearsampp-build/bins/php/{bundle.release}/bearsampp-php-{version}-{bundle.release}.{7z|zip}` + +--- + +### `releaseBuild` + +**Group:** build +**Description:** Execute the release build process + +**Usage:** +```bash +gradle releaseBuild -PbundleVersion=8.3.15 +``` + +**Parameters:** + +| Parameter | Type | Required | Description | Example | +|-------------------|----------|----------|--------------------------------|--------------| +| `bundleVersion` | String | Yes | PHP version to build | `8.3.15` | + +**Note:** This task is typically called by the `release` task. Use `release` instead for normal builds. + +--- + +### `packageRelease` + +**Group:** build +**Description:** Package release into archive (7z or zip) including the version folder at the root of the archive + +**Usage:** +```bash +gradle packageRelease -PbundleVersion=8.3.15 +``` + +**Notes:** +- Chooses archiver based on `bundle.format` in `build.properties` (`7z` or `zip`). +- Archive content layout mirrors module-bruno: + - Root folder: `{bundle.release}/` (e.g., `2025.10.31/`) + - Inside root: `bin/php{version}/` and `bin/archived/` + - Optional: `releases.properties` at the same level as `bin/` +- Output: `bearsampp-build/bins/php/{bundle.release}/bearsampp-php-{version}-{bundle.release}.{7z|zip}` + +--- + +### `packageRelease7z` + +**Group:** build +**Description:** Package release into a `.7z` archive (requires 7-Zip in PATH). Ensures the top-level folder inside the archive is `{bundle.release}/` containing `bin/` and `bin/archived/`. + +**Usage:** +```bash +gradle packageRelease7z -PbundleVersion=8.3.15 +``` + +--- + +### `packageReleaseZip` + +**Group:** build +**Description:** Package release into a `.zip` archive using Gradle's native Zip task. Ensures the top-level folder inside the archive is `{bundle.release}/` containing `bin/` and `bin/archived/`. + +**Usage:** +```bash +gradle packageReleaseZip -PbundleVersion=8.3.15 +``` + +--- + +### `clean` + +**Group:** build +**Description:** Clean build artifacts and temporary files + +**Usage:** +```bash +gradle clean +``` + +**Cleans:** +- `build/` directory +- `tmp/` directory +- All temporary build files + +**Output:** +``` +Cleaned: E:/Bearsampp-development/module-php/build +Cleaned: E:/Bearsampp-development/module-php/tmp +[SUCCESS] Build artifacts cleaned +``` + +--- + +## Verification Tasks + +### `verify` + +**Group:** verification +**Description:** Verify build environment and dependencies + +**Usage:** +```bash +gradle verify +``` + +**Checks:** + +| Check | Description | Required | +|------------------------|------------------------------------------|----------| +| Java 8+ | Java version 8 or higher | Yes | +| build.properties | Build configuration file exists | Yes | +| releases.properties | Release definitions file exists | Yes | +| pear directory | PEAR installation directory exists | Yes | +| pear-install.bat | PEAR installation script exists | Yes | +| bin directory | PHP versions directory exists | Yes | + +**Output:** +``` +Environment Check Results: +------------------------------------------------------------ + [PASS] Java 8+ + [PASS] build.properties + [PASS] releases.properties + [PASS] pear directory + [PASS] pear-install.bat + [PASS] bin directory +------------------------------------------------------------ + +[SUCCESS] All checks passed! Build environment is ready. +``` + +--- + +### `validateProperties` + +**Group:** verification +**Description:** Validate build.properties configuration + +**Usage:** +```bash +gradle validateProperties +``` + +**Validates:** + +| Property | Required | Description | +|-------------------|----------|--------------------------------| +| `bundle.name` | Yes | Name of the bundle | +| `bundle.release` | Yes | Release version | +| `bundle.type` | Yes | Type of bundle | +| `bundle.format` | Yes | Archive format | + +**Output:** +``` +[SUCCESS] All required properties are present: + bundle.name = php + bundle.release = 2025.10.31 + bundle.type = bins + bundle.format = 7z +``` + +--- + +### `validateDllArchitecture` + +**Group:** verification +**Description:** Validate DLL architecture (64-bit check) + +**Usage:** +```bash +gradle validateDllArchitecture -PdllFile=path/to/file.dll +``` + +**Parameters:** + +| Parameter | Type | Required | Description | Example | +|--------------|----------|----------|--------------------------------|--------------------------| +| `dllFile` | String | Yes | Path to DLL file to validate | `ext/php_redis.dll` | + +**Output:** +``` +Validating architecture for: php_redis.dll +MachineType: 64-bit +[SUCCESS] DLL is 64-bit compatible +``` + +--- + +## Information Tasks + +### `info` + +**Group:** help +**Description:** Display build configuration information + +**Usage:** +```bash +gradle info +``` + +**Displays:** +- Project information (name, version, description) +- Bundle properties (name, release, type, format) +- Paths (project dir, root dir, dev path, PHP ext, PEAR install) +- Java information (version, home) +- Gradle information (version, home) +- Available task groups +- Quick start commands + +--- + +### `phpInfo` + +**Group:** help +**Description:** Display PHP-specific build information + +**Usage:** +```bash +gradle phpInfo +``` + +**Displays:** +- PHP extension processing information +- PEAR installation information +- Dependencies information +- Architecture verification information +- Configuration files +- Useful commands + +--- + +### `listVersions` + +**Group:** help +**Description:** List all available bundle versions in bin/ directory + +**Usage:** +```bash +gradle listVersions +``` + +**Output:** +``` +Available php versions in bin/: +------------------------------------------------------------ + 8.1.32 + 8.1.33 + 8.2.28 + 8.2.29 + 8.3.20 + 8.3.24 + 8.3.26 + 8.3.27 + 8.4.10 + 8.4.11 + 8.4.13 + 8.4.14 +------------------------------------------------------------ +Total versions: 12 + +To build a specific version: + gradle release -PbundleVersion=8.4.14 +``` + +--- + +### `listReleases` + +**Group:** help +**Description:** List all available releases from releases.properties + +**Usage:** +```bash +gradle listReleases +``` + +**Output:** +``` +Available PHP Releases: +-------------------------------------------------------------------------------- + 8.1.32 -> https://windows.php.net/downloads/releases/php-8.1.32-Win32-vs16-x64.zip + 8.1.33 -> https://windows.php.net/downloads/releases/php-8.1.33-Win32-vs16-x64.zip + ... +-------------------------------------------------------------------------------- +Total releases: 50 +``` + +--- + +### `listExtensions` + +**Group:** help +**Description:** List PHP extensions configured in bin directories + +**Usage:** +```bash +gradle listExtensions +``` + +**Output:** +``` +Scanning for PHP extension configurations... +================================================================================ + +PHP 8.3.15: +-------------------------------------------------------------------------------- + imagick -> https://windows.php.net/downloads/pecl/releases/imagick/3.7.0/php_imagick-3.7.0-8.3-ts-vs16-x64.zip + redis -> https://windows.php.net/downloads/pecl/releases/redis/6.0.2/php_redis-6.0.2-8.3-ts-vs16-x64.zip + xdebug -> https://xdebug.org/files/php_xdebug-3.3.1-8.3-vs16-x86_64.dll +================================================================================ +``` + +--- + +### `listPearConfig` + +**Group:** help +**Description:** List PEAR configurations in bin directories + +**Usage:** +```bash +gradle listPearConfig +``` + +**Output:** +``` +Scanning for PEAR configurations... +================================================================================ + +PHP 8.3.15: +-------------------------------------------------------------------------------- + phppear.pear -> https://pear.php.net/install-pear-nozlib.phar +================================================================================ +``` + +--- + +### `listDependencies` + +**Group:** help +**Description:** List dependencies configured in bin directories + +**Usage:** +```bash +gradle listDependencies +``` + +**Output:** +``` +Scanning for dependency configurations... +================================================================================ + +PHP 8.3.15: +-------------------------------------------------------------------------------- + imagemagick -> https://imagemagick.org/archive/binaries/ImageMagick-7.1.1-28-portable-Q16-HDRI-x64.zip +================================================================================ +``` + +--- + +## Task Dependencies + +### Task Execution Order + +``` +release + └── releaseBuild + ├── (validates environment) + ├── (creates directories) + ├── (copies base files) + ├── (processes extensions) + ├── (processes PEAR) + └── (processes dependencies) +``` + +### Task Groups + +| Group | Tasks | +|------------------|----------------------------------------------------------------------------| +| **build** | `release`, `releaseBuild`, `clean` | +| **verification** | `verify`, `validateProperties`, `validateDllArchitecture` | +| **help** | `info`, `phpInfo`, `listVersions`, `listReleases`, `listExtensions`, `listPearConfig`, `listDependencies` | + +--- + +## Task Examples + +### Example 1: Complete Build Workflow + +```bash +# 1. Verify environment +gradle verify + +# 2. List available versions +gradle listVersions + +# 3. Check extensions for a version +gradle listExtensions + +# 4. Build the release +gradle release -PbundleVersion=8.3.15 + +# 5. Clean up +gradle clean +``` + +--- + +### Example 2: Debugging a Build + +```bash +# Run with info logging +gradle release -PbundleVersion=8.3.15 --info + +# Run with debug logging +gradle release -PbundleVersion=8.3.15 --debug + +# Run with stack trace on error +gradle release -PbundleVersion=8.3.15 --stacktrace +``` + +--- + +### Example 3: Validation Workflow + +```bash +# Validate build properties +gradle validateProperties + +# Verify environment +gradle verify + +# Validate a specific DLL +gradle validateDllArchitecture -PdllFile=ext/php_redis.dll +``` + +--- + +### Example 4: Information Gathering + +```bash +# Get build info +gradle info + +# Get PHP-specific info +gradle phpInfo + +# List all available versions +gradle listVersions + +# List all releases +gradle listReleases + +# List extensions for all versions +gradle listExtensions + +# List PEAR configs +gradle listPearConfig + +# List dependencies +gradle listDependencies +``` + +--- + +## Task Options + +### Common Gradle Options + +| Option | Description | Example | +|---------------------|------------------------------------------|------------------------------------------| +| `--info` | Set log level to INFO | `gradle release --info` | +| `--debug` | Set log level to DEBUG | `gradle release --debug` | +| `--stacktrace` | Print stack trace on error | `gradle release --stacktrace` | +| `--scan` | Create build scan | `gradle release --scan` | +| `--dry-run` | Show what would be executed | `gradle release --dry-run` | +| `--parallel` | Execute tasks in parallel | `gradle release --parallel` | +| `--offline` | Execute build without network access | `gradle release --offline` | + +--- + +## Task Properties + +### Project Properties + +Set via `-P` flag: + +| Property | Type | Description | Example | +|-------------------|----------|--------------------------------|------------------------------------------| +| `bundleVersion` | String | PHP version to build | `-PbundleVersion=8.3.15` | +| `dllFile` | String | DLL file to validate | `-PdllFile=ext/php_redis.dll` | + +### System Properties + +Set via `-D` flag: + +| Property | Type | Description | Example | +|-------------------|----------|--------------------------------|------------------------------------------| +| `org.gradle.daemon` | Boolean | Enable Gradle daemon | `-Dorg.gradle.daemon=true` | +| `org.gradle.parallel` | Boolean | Enable parallel execution | `-Dorg.gradle.parallel=true` | + +--- + +**Last Updated**: 2025-01-31 +**Version**: 2025.10.31 + diff --git a/README.md b/README.md index 6af395bf..f43858ba 100644 --- a/README.md +++ b/README.md @@ -5,13 +5,67 @@ This is a module of [Bearsampp project](https://github.com/bearsampp/bearsampp) involving PHP. -## Documentation and downloads +## Build System -https://bearsampp.com/module/php +This project uses **Gradle** as its build system. The legacy Ant build has been fully replaced with a modern, pure Gradle implementation. + +### Quick Start + +```bash +# Display build information +gradle info + +# List all available tasks +gradle tasks + +# Verify build environment +gradle verify + +# Build a release (interactive) +gradle release + +# Build a specific version (non-interactive) +gradle release -PbundleVersion=8.3.15 + +# Clean build artifacts +gradle clean +``` + +### Prerequisites + +| Requirement | Version | Purpose | +|-------------------|---------------|------------------------------------------| +| **Java** | 8+ | Required for Gradle execution | +| **Gradle** | 8.0+ | Build automation tool | +| **PowerShell** | 5.0+ | DLL architecture validation | +| **7-Zip** | Latest | Archive extraction (optional) | + +### Available Tasks + +| Task | Description | +|-----------------------|--------------------------------------------------| +| `release` | Build release package (interactive/non-interactive) | +| `clean` | Clean build artifacts and temporary files | +| `verify` | Verify build environment and dependencies | +| `info` | Display build configuration information | +| `listVersions` | List available bundle versions in bin/ | +| `listExtensions` | List PHP extensions configured in bin/ | +| `validateProperties` | Validate build.properties configuration | + +For complete documentation, see [.gradle-docs/README.md](.gradle-docs/README.md) + +## Documentation + +- **Build Documentation**: [.gradle-docs/README.md](.gradle-docs/README.md) +- **Task Reference**: [.gradle-docs/TASKS.md](.gradle-docs/TASKS.md) +- **Configuration Guide**: [.gradle-docs/CONFIGURATION.md](.gradle-docs/CONFIGURATION.md) +- **API Reference**: [.gradle-docs/API.md](.gradle-docs/API.md) +- **Module Downloads**: https://bearsampp.com/module/php ## Issues Issues must be reported on [Bearsampp repository](https://github.com/bearsampp/bearsampp/issues). ## Statistics + ![Alt](https://repobeats.axiom.co/api/embed/2b56dc0b1aac6a6280b8051a41421d4fbb89ef49.svg "Repobeats analytics image") diff --git a/build.gradle b/build.gradle new file mode 100644 index 00000000..e27b6142 --- /dev/null +++ b/build.gradle @@ -0,0 +1,1690 @@ +/* + * Bearsampp Module PHP - Pure Gradle Build + * + * This is a pure Gradle build configuration that replaces the legacy Ant build system. + * All build logic has been converted to native Gradle tasks. + * + * Usage: + * gradle tasks - List all available tasks + * gradle release - Interactive release (prompts for version) + * gradle release -PbundleVersion=8.3.15 - Non-interactive release + * gradle clean - Clean build artifacts + * gradle info - Display build information + * gradle verify - Verify build environment + */ + +plugins { + id 'base' +} + +// Load build properties +def buildProps = new Properties() +file('build.properties').withInputStream { buildProps.load(it) } + +// Project information +group = 'com.bearsampp.modules' +version = buildProps.getProperty('bundle.release', '1.0.0') +description = "Bearsampp Module - ${buildProps.getProperty('bundle.name', 'php')}" + +// Define project paths +ext { + projectBasedir = projectDir.absolutePath + rootDir = projectDir.parent + devPath = file("${rootDir}/dev").absolutePath + buildPropertiesFile = file('build.properties').absolutePath + + // Bundle properties from build.properties + bundleName = buildProps.getProperty('bundle.name', 'php') + bundleRelease = buildProps.getProperty('bundle.release', '1.0.0') + bundleType = buildProps.getProperty('bundle.type', 'bins') + bundleFormat = buildProps.getProperty('bundle.format', '7z') + + // External build base path precedence: build.properties (build.path) -> env(BEARSAMPP_BUILD_PATH) -> default /bearsampp-build + def buildPathFromProps = (buildProps.getProperty('build.path', '') ?: '').trim() + def buildPathFromEnv = System.getenv('BEARSAMPP_BUILD_PATH') ?: '' + def defaultBuildPath = "${rootDir}/bearsampp-build" + buildBasePath = buildPathFromProps ? buildPathFromProps : (buildPathFromEnv ? buildPathFromEnv : defaultBuildPath) + + // Shared external tmp tree (MySQL pattern) + buildTmpPath = file("${buildBasePath}/tmp").absolutePath + bundleTmpPrepPath = file("${buildTmpPath}/bundles_prep/${bundleType}/${bundleName}").absolutePath + bundleTmpBuildPath = file("${buildTmpPath}/bundles_build/${bundleType}/${bundleName}").absolutePath + bundleTmpSrcPath = file("${buildTmpPath}/bundles_src").absolutePath + bundleTmpDownloadPath = file("${buildTmpPath}/downloads/${bundleName}").absolutePath + bundleTmpExtractPath = file("${buildTmpPath}/extract/${bundleName}").absolutePath + + // Final external output path for archives (e.g., bearsampp-build/bins/php/2025.10.31) + moduleBuildOutputPath = file("${buildBasePath}/${bundleType}/${bundleName}/${bundleRelease}").absolutePath + + // PHP-specific paths (static in repo) + phpExtPath = file("${projectDir}/ext").absolutePath + pearInstallPath = file("${projectDir}/pear").absolutePath +} + +// Verify dev path exists +if (!file(ext.devPath).exists()) { + logger.warn("Dev path not found: ${ext.devPath}. Some tasks may not work correctly.") +} + +// Configure repositories for dependencies +repositories { + mavenCentral() +} + +// ============================================================================ +// GRADLE NATIVE TASKS - Pure Gradle Implementation +// ============================================================================ + +// Task: Display build information +tasks.register('info') { + group = 'help' + description = 'Display build configuration information' + + // Capture values at configuration time to avoid deprecation warnings + def projectName = project.name + def projectVersion = project.version + def projectDescription = project.description + def gradleVersion = gradle.gradleVersion + def gradleHome = gradle.gradleHomeDir + + doLast { + println """ + ================================================================ + Bearsampp Module PHP - Build Info + ================================================================ + + Project: ${projectName} + Version: ${projectVersion} + Description: ${projectDescription} + + Bundle Properties: + Name: ${bundleName} + Release: ${bundleRelease} + Type: ${bundleType} + Format: ${bundleFormat} + + Paths: + Project Dir: ${projectBasedir} + Root Dir: ${rootDir} + Dev Path: ${devPath} + Build Base: ${buildBasePath} + Output Dir: ${moduleBuildOutputPath} + Tmp Root: ${buildTmpPath} + Tmp Prep: ${bundleTmpPrepPath} + Tmp Build: ${bundleTmpBuildPath} + Tmp Src: ${bundleTmpSrcPath} + Downloads: ${bundleTmpDownloadPath} + Extract: ${bundleTmpExtractPath} + PHP Ext: ${phpExtPath} + PEAR Install: ${pearInstallPath} + + Java: + Version: ${JavaVersion.current()} + Home: ${System.getProperty('java.home')} + + Gradle: + Version: ${gradleVersion} + Home: ${gradleHome} + + Available Task Groups: + * build - Build and package tasks + * help - Help and information tasks + * verification - Verification and validation tasks + + Quick Start: + gradle tasks - List all available tasks + gradle info - Show this information + gradle release - Interactive release build + gradle release -PbundleVersion=8.3.15 - Non-interactive release + gradle clean - Clean build artifacts + gradle verify - Verify build environment + gradle listExtensions - List PHP extensions to build + """.stripIndent() + } +} + +// Task: Enhanced clean task +tasks.named('clean') { + group = 'build' + description = 'Clean build artifacts and temporary files' + + doLast { + // Clean Gradle build directory + def buildDir = file("${projectDir}/build") + if (buildDir.exists()) { + delete buildDir + println "Cleaned: ${buildDir}" + } + + // Clean temporary directories + def tmpDir = file(buildTmpPath) + if (tmpDir.exists()) { + delete tmpDir + println "Cleaned: ${tmpDir}" + } + + // Clean Gradle-specific temp files + def gradleBundleVersion = file("${buildTmpPath}/.gradle-bundleVersion") + if (gradleBundleVersion.exists()) { + delete gradleBundleVersion + println "Cleaned: ${gradleBundleVersion.name}" + } + + def phpExtensionsTmp = file("${buildTmpPath}/php_extensions.tmp") + if (phpExtensionsTmp.exists()) { + delete phpExtensionsTmp + println "Cleaned: ${phpExtensionsTmp.name}" + } + + println "[SUCCESS] Build artifacts cleaned" + } +} + +// Task: Verify build environment +tasks.register('verify') { + group = 'verification' + description = 'Verify build environment and dependencies' + + doLast { + println "Verifying build environment for module-php..." + + def checks = [:] + + // Check Java version + def javaVersion = JavaVersion.current() + checks['Java 8+'] = javaVersion >= JavaVersion.VERSION_1_8 + + // Check required files + checks['build.properties'] = file('build.properties').exists() + checks['releases.properties'] = file('releases.properties').exists() + + // Check PHP-specific directories + checks['pear directory'] = file(pearInstallPath).exists() + checks['pear-install.bat'] = file("${pearInstallPath}/pear-install.bat").exists() + + // Check bin directory + checks['bin directory'] = file("${projectDir}/bin").exists() + + println "\nEnvironment Check Results:" + println "-".multiply(60) + checks.each { name, passed -> + def status = passed ? "[PASS]" : "[FAIL]" + println " ${status.padRight(10)} ${name}" + } + println "-".multiply(60) + + def allPassed = checks.values().every { it } + if (allPassed) { + println "\n[SUCCESS] All checks passed! Build environment is ready." + println "\nYou can now run:" + println " gradle release - Interactive release" + println " gradle release -PbundleVersion=8.3.15 - Non-interactive release" + } else { + println "\n[WARNING] Some checks failed. Please review the requirements." + throw new GradleException("Build environment verification failed") + } + } +} + +// Task: Validate architecture of DLL files +tasks.register('validateDllArchitecture') { + group = 'verification' + description = 'Validate DLL architecture (64-bit check)' + + doLast { + def dllFile = project.findProperty('dllFile') + if (!dllFile) { + throw new GradleException("Please specify -PdllFile= to validate") + } + + def dll = file(dllFile) + if (!dll.exists()) { + throw new GradleException("DLL file not found: ${dllFile}") + } + + println "Validating architecture for: ${dll.name}" + + def outputFile = file("${buildTmpPath}/sigcheck_output.txt") + outputFile.parentFile.mkdirs() + + def psCommand = """ +try { + \$bytes = [System.IO.File]::ReadAllBytes('${dll.absolutePath}'); + \$peOffset = [System.BitConverter]::ToInt32(\$bytes, 60); + \$machineType = [System.BitConverter]::ToUInt16(\$bytes, \$peOffset + 4); + if (\$machineType -eq 0x8664) { + Write-Output 'MachineType: 64-bit' + } elseif (\$machineType -eq 0x014c) { + Write-Output 'MachineType: 32-bit' + } else { + Write-Output 'MachineType: Unknown' + } +} catch { + Write-Output 'MachineType: Error' +} +""".trim() + + def result = exec { + executable 'powershell.exe' + args '-NoLogo', '-NoProfile', '-Command', psCommand + standardOutput = new ByteArrayOutputStream() + ignoreExitValue = true + } + + def output = result.standardOutput.toString().trim() + println output + + if (!output.contains('64-bit')) { + throw new GradleException("DLL ${dll.name} is not 64-bit architecture. Build halted.") + } + + println "[SUCCESS] DLL is 64-bit compatible" + } +} + +// ============================================================================ +// HELPER FUNCTIONS +// ============================================================================ + +// 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 +} + +// Helper function to calculate hash +def calculateHash(File file, String algorithm) { + def digest = java.security.MessageDigest.getInstance(algorithm) + file.withInputStream { stream -> + def buffer = new byte[8192] + def bytesRead + while ((bytesRead = stream.read(buffer)) != -1) { + digest.update(buffer, 0, bytesRead) + } + } + return digest.digest().collect { String.format('%02x', it) }.join('') +} + +// Helper function to generate hash files +def generateHashFiles(File file) { + if (!file.exists()) { + throw new GradleException("File not found for hashing: ${file}") + } + + // 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') + sha512File.text = "${sha512Hash} ${file.name}\n" + println " Created: ${sha512File.name}" +} + +// Helper: Fetch php.properties from modules-untouched repository +def fetchModulesUntouchedProperties() { + def propsUrl = "https://raw.githubusercontent.com/Bearsampp/modules-untouched/main/modules/php.properties" + + println "Fetching php.properties from modules-untouched repository..." + println " URL: ${propsUrl}" + + def tempFile = file("${bundleTmpDownloadPath}/php-untouched.properties") + tempFile.parentFile.mkdirs() + + try { + // Download using Java URL connection + new URL(propsUrl).withInputStream { input -> + tempFile.withOutputStream { output -> + output << input + } + } + + 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 php.properties from modules-untouched: ${e.message}" + println " Will fall back to standard URL format if needed" + return null + } +} + +// Helper: Download PHP binaries from modules-untouched repository +def downloadFromModulesUntouched(String version, File destDir) { + def untouchedProps = fetchModulesUntouchedProperties() + def untouchedUrl = null + + if (untouchedProps) { + untouchedUrl = untouchedProps.getProperty(version) + if (untouchedUrl) { + println "Found version ${version} in modules-untouched php.properties" + println "Downloading from:" + println " ${untouchedUrl}" + } else { + println "Version ${version} not found in modules-untouched php.properties" + println "Attempting to construct URL based on standard format..." + // Fallback to constructed URL + untouchedUrl = "https://windows.php.net/downloads/releases/php-${version}-Win32-vs16-x64.zip" + println " ${untouchedUrl}" + } + } else { + println "Could not fetch php.properties, using standard URL format..." + // Fallback to constructed URL + untouchedUrl = "https://windows.php.net/downloads/releases/php-${version}-Win32-vs16-x64.zip" + 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..." + new URL(untouchedUrl).withInputStream { input -> + downloadedFile.withOutputStream { output -> + output << input + } + } + println " ✓ Downloaded: ${downloadedFile.name}" + } else { + println " ✓ Already downloaded: ${downloadedFile.name}" + } + + return downloadedFile +} + +// Helper: Download and extract PHP binaries +def downloadAndExtractPhp(String version, File destDir) { + // Download from modules-untouched + def downloadedFile = downloadFromModulesUntouched(version, destDir) + + // Extract the archive + def extractDir = file(bundleTmpExtractPath) + extractDir.mkdirs() + println " Extracting archive..." + def extractPath = file("${extractDir}/${version}") + if (extractPath.exists()) { + delete extractPath + } + extractPath.mkdirs() + + // Extract using Gradle's zipTree + copy { + from zipTree(downloadedFile) + into extractPath + } + + // Find the PHP directory (it might be nested) + def phpDir = findPhpDirectory(extractPath) + if (!phpDir) { + throw new GradleException("Could not find PHP directory in extracted archive") + } + + println " Extracted to: ${phpDir}" + return phpDir +} + +// Helper: Find PHP directory containing php.exe +def findPhpDirectory(File searchDir) { + // Case 1: Check if php.exe is directly in this directory + def phpExe = new File(searchDir, 'php.exe') + if (phpExe.exists()) { + return searchDir + } + + // Case 2: Recursively search for a directory containing php.exe + File found = null + def stack = new ArrayDeque() + stack.push(searchDir) + + while (!stack.isEmpty() && !found) { + def current = stack.pop() + def children = current.listFiles() + if (children) { + for (def child : children) { + if (child.isDirectory()) { + def exe = new File(child, 'php.exe') + if (exe.exists()) { + found = child + break + } + stack.push(child) + } + } + } + } + + return found +} + +// Task: Download file helper +def downloadFile(String url, File destDir) { + if (!url) { + throw new GradleException("Download URL is null or empty") + } + + def fileName = url.tokenize('/').last() + def destFile = new File(destDir, fileName) + + if (!destFile.exists()) { + println "Downloading: ${url}" + destFile.parentFile.mkdirs() + + new URL(url).withInputStream { input -> + destFile.withOutputStream { output -> + output << input + } + } + println "Downloaded to: ${destFile}" + } else { + println "Already exists: ${destFile}" + } + + return destFile +} + +// Task: Extract archive helper +def extractArchive(File archive, File destDir) { + destDir.mkdirs() + + if (archive.name.endsWith('.zip')) { + copy { + from zipTree(archive) + into destDir + } + } else if (archive.name.endsWith('.7z')) { + // Find 7z executable + def sevenZipExe = find7ZipExecutable() + if (!sevenZipExe) { + throw new GradleException("7-Zip not found. Cannot extract .7z archive: ${archive.name}") + } + + // Extract using ProcessBuilder + def command = [ + sevenZipExe, + 'x', + archive.absolutePath, + "-o${destDir.absolutePath}", + '-y' + ] + + def process = new ProcessBuilder(command as String[]) + .redirectErrorStream(true) + .start() + + def output = new StringBuilder() + process.inputStream.eachLine { line -> + output.append(line).append('\n') + } + + def exitCode = process.waitFor() + if (exitCode != 0) { + throw new GradleException("7-Zip extraction failed with exit code: ${exitCode}\nOutput: ${output}") + } + } + + println "Extracted to: ${destDir}" +} + +// Helper methods for version discovery and selection +def findAvailableVersions = { + def binDir = new File(projectDir, 'bin') + def archivedDir = new File(projectDir, 'bin/archived') + def versions = [] as List + + if (binDir.exists()) { + versions.addAll( + (binDir.listFiles() ?: []) + .findAll { it.isDirectory() && it.name.startsWith(bundleName) && it.name != 'archived' } + .collect { it.name.replace(bundleName, '') } + ) + } + + if (archivedDir.exists()) { + versions.addAll( + (archivedDir.listFiles() ?: []) + .findAll { it.isDirectory() && it.name.startsWith(bundleName) } + .collect { it.name.replace(bundleName, '') } + ) + } + + return versions.unique() +} + +def latestVersion = { List versions -> + if (versions.isEmpty()) return null + // Compare numerically by dot-separated parts + return versions.max { a, b -> + def pa = a.split('\\.').collect { it as int } + def pb = b.split('\\.').collect { it as int } + def len = Math.max(pa.size(), pb.size()) + for (int i = 0; i < len; i++) { + def ai = i < pa.size() ? pa[i] : 0 + def bi = i < pb.size() ? pb[i] : 0 + if (ai != bi) return ai <=> bi + } + return 0 + } +} + +// Task: Resolve version (interactive by default; supports -PbundleVersion and '*') +tasks.register('resolveVersion') { + group = 'build' + description = 'Resolve bundleVersion (interactive by default, or use -PbundleVersion=*,)' + + // Capture properties at configuration time + def versionProperty = project.findProperty('bundleVersion') + def projectDirPath = projectDir + def bundleNameValue = bundleName + + doLast { + def supplied = versionProperty as String + // Aggregate both bin/ and bin/archived/ and display with location tags + def all = findAvailableVersions().sort { a, b -> + def pa = a.split('\\.').collect { it as int } + def pb = b.split('\\.').collect { it as int } + for (int i=0; i bi + } + return 0 + } + def inBin = new File(projectDir, 'bin').exists() ? (new File(projectDir, 'bin').listFiles() + ?.findAll { it.isDirectory() && it.name.startsWith(bundleName) && it.name != 'archived' } + ?.collect { it.name.replace(bundleName, '') } ?: []) : [] + def inArchived = new File(projectDir, 'bin/archived').exists() ? (new File(projectDir, 'bin/archived').listFiles() + ?.findAll { it.isDirectory() && it.name.startsWith(bundleName) } + ?.collect { it.name.replace(bundleName, '') } ?: []) : [] + + String resolved + if (supplied) { + if (supplied == '*') { + resolved = latestVersion(all) + if (!resolved) { + throw new GradleException("No versions found under bin/ to resolve latest from.") + } + println "Resolved latest version: ${resolved}" + } else { + resolved = supplied + } + } else { + println "=".multiply(70) + println "Available ${bundleName} versions (index, version, location):" + println "-".multiply(70) + all.eachWithIndex { v, idx -> + def indexStr = String.format('%2d', idx + 1) + def tag = inBin.contains(v) && inArchived.contains(v) ? '[bin + bin/archived]' : (inBin.contains(v) ? '[bin]' : (inArchived.contains(v) ? '[bin/archived]' : '[unknown]')) + println " ${indexStr}. ${v.padRight(12)} ${tag}" + } + println "-".multiply(70) + print "\nEnter version to build (index or version string): " + System.out.flush() + def reader = new BufferedReader(new InputStreamReader(System.in)) + def input = reader.readLine()?.trim() + if (!input) { + throw new GradleException("No version specified") + } + if (input.isInteger()) { + def idx = input.toInteger() + if (idx < 1 || idx > all.size()) { + throw new GradleException("Invalid index: ${input}. Choose 1..${all.size()} or enter a version string.") + } + resolved = all[idx - 1] + } else { + resolved = input + } + } + + // Validate existence in bin/ or bin/archived/ + def bundlePath = new File(projectDir, "bin/${bundleName}${resolved}") + if (!bundlePath.exists()) { + def archivedPath = new File(projectDir, "bin/archived/${bundleName}${resolved}") + if (archivedPath.exists()) { + bundlePath = archivedPath + } else { + def listing = all.collect { " - ${it}" }.join('\n') + throw new GradleException("Bundle version not found in bin/ or bin/archived/: ${bundleName}${resolved}\n\nAvailable versions:\n${listing}") + } + } + + // Expose to subsequent tasks as extra property + // Store in a shared location that can be accessed by other tasks + def propsFile = file("${buildTmpPath}/.gradle-bundleVersion") + propsFile.parentFile.mkdirs() + propsFile.text = resolved + println "\nSelected version: ${resolved}\n" + } +} + +// Task: Main release task (interactive by default; non‑interactive when -PbundleVersion provided) +tasks.register('release') { + group = 'build' + description = 'Build release package (interactive by default; -PbundleVersion=* or X.Y.Z for non-interactive)' + + // Always clean before building to ensure fresh build + dependsOn 'clean', 'resolveVersion', 'packageRelease' +} + +// Task: Interactive release (explicitly prompts) +tasks.register('releaseInteractive') { + group = 'build' + description = 'Interactive release (prompts for version)' + dependsOn 'packageRelease' + + doFirst { + def binDir = new File(projectDir, 'bin') + def inBin = binDir.exists() ? binDir.listFiles() + .findAll { it.isDirectory() && it.name.startsWith(bundleName) } + .collect { it.name.replace(bundleName, '') } : [] + def archivedDir = new File(projectDir, 'bin/archived') + def inArchived = archivedDir.exists() ? archivedDir.listFiles() + .findAll { it.isDirectory() && it.name.startsWith(bundleName) } + .collect { it.name.replace(bundleName, '') } : [] + def all = (inBin + inArchived).toSet().toList().sort { a, b -> + def pa = a.split('\\.').collect { it as int } + def pb = b.split('\\.').collect { it as int } + for (int i=0; i bi + } + return 0 + } + + println "=".multiply(70) + println "Available ${bundleName} versions (index, version, location):" + println "-".multiply(70) + all.eachWithIndex { v, idx -> + def indexStr = String.format('%2d', idx + 1) + def tag = inBin.contains(v) && inArchived.contains(v) ? '[bin + bin/archived]' : (inBin.contains(v) ? '[bin]' : (inArchived.contains(v) ? '[bin/archived]' : '[unknown]')) + println " ${indexStr}. ${v.padRight(12)} ${tag}" + } + println "-".multiply(70) + + print "\nEnter version to build (index or version string): " + System.out.flush() + + def reader = new BufferedReader(new InputStreamReader(System.in)) + def input = reader.readLine()?.trim() + if (!input) { + throw new GradleException("No version specified") + } + + def versionToBuild + if (input.isInteger()) { + def idx = input.toInteger() + if (idx < 1 || idx > all.size()) { + throw new GradleException("Invalid index: ${input}. Choose 1..${all.size()} or enter a version string.") + } + versionToBuild = all[idx - 1] + } else { + versionToBuild = input + } + + // Validate selected version exists in bin/ or bin/archived/ + def bundlePath = new File(projectDir, "bin/${bundleName}${versionToBuild}") + if (!bundlePath.exists()) { + def archivedPath = new File(projectDir, "bin/archived/${bundleName}${versionToBuild}") + if (!archivedPath.exists()) { + def listing = all.collect { " - ${it}" }.join('\n') + throw new GradleException("Bundle version not found in bin/ or bin/archived/: ${bundleName}${versionToBuild}\n\nAvailable versions:\n${listing}") + } + } + + // Expose version to dependent tasks + project.ext.set('bundleVersion', versionToBuild) + println "\nBuilding release for ${bundleName} version ${versionToBuild}...\n" + } +} + +// Task: Actual release build logic +tasks.register('releaseBuild') { + group = 'build' + description = 'Execute the release build process' + dependsOn 'resolveVersion' + + // Capture version at configuration time to avoid deprecation warnings + def versionProvider = providers.provider { + def fromProp = project.findProperty('bundleVersion') as String + if (fromProp) return fromProp + // Read from file written by resolveVersion task + def propsFile = file("${buildTmpPath}/.gradle-bundleVersion") + if (propsFile.exists()) { + return propsFile.text.trim() + } + return null + } + + doLast { + def versionToBuild = versionProvider.getOrNull() + if (!versionToBuild) { + throw new GradleException("bundleVersion property not set") + } + + + // Resolve config folder from bin/ or bin/archived/ (contains exts.properties, pear.properties, etc.) + def bundlePath = new File(projectDir, "bin/${bundleName}${versionToBuild}") + if (!bundlePath.exists()) { + def archivedPath = new File(projectDir, "bin/archived/${bundleName}${versionToBuild}") + if (archivedPath.exists()) { + bundlePath = archivedPath + } else { + throw new GradleException("Bundle folder not found in bin/ or bin/archived/: ${bundleName}${versionToBuild}") + } + } + + def bundleFolder = bundlePath.name + def bundleVersion = bundleFolder.replace(bundleName, '') + + println "Processing bundle: ${bundleFolder}" + println "Version: ${bundleVersion}" + + // Determine source paths for PHP binaries + def bundleSrcDest = bundlePath + def bundleSrcFinal = bundleSrcDest + + // Check if php.exe exists in bin/ directory (it won't - only config files are there) + def phpExe = file("${bundleSrcFinal}/php.exe") + if (!phpExe.exists()) { + // PHP binaries not found in bin/ - check if already downloaded to bearsampp-build/tmp + def tmpExtractPath = file("${bundleTmpExtractPath}/${bundleVersion}") + def tmpPhpDir = findPhpDirectory(tmpExtractPath) + + if (tmpPhpDir && tmpPhpDir.exists()) { + println "Using cached PHP binaries from bearsampp-build/tmp" + bundleSrcFinal = tmpPhpDir + } else { + // Download and extract to bearsampp-build/tmp + println "" + println "PHP binaries not found" + println "Downloading PHP ${bundleVersion}..." + println "" + + try { + // Download and extract to bearsampp-build/tmp + bundleSrcFinal = downloadAndExtractPhp(bundleVersion, file(bundleTmpExtractPath)) + } catch (Exception e) { + throw new GradleException(""" + Failed to download PHP binaries: ${e.message} + + You can manually download and extract PHP binaries to: + ${bundleSrcDest}/ + + Or check that version ${bundleVersion} exists in modules-untouched php.properties + """.stripIndent()) + } + } + } + + // Verify php.exe exists + phpExe = file("${bundleSrcFinal}/php.exe") + if (!phpExe.exists()) { + throw new GradleException("php.exe not found at ${phpExe}") + } + + println "Source folder: ${bundleSrcFinal}" + println "" + + // Prepare PHP directory + def phpPrepPath = new File(bundleTmpPrepPath, bundleFolder) + delete phpPrepPath + phpPrepPath.mkdirs() + + println "Preparation path: ${phpPrepPath}" + + // Copy base PHP binaries + println "Copying base PHP files..." + copy { + from bundleSrcFinal + into phpPrepPath + } + + // Copy config files from bin/ directory (bearsampp.conf, php.ini, etc.) + println "Copying configuration files..." + copy { + from bundleSrcDest + into phpPrepPath + exclude 'deps/**' + } + + // Process extensions if exts.properties exists + def extsFile = new File(bundlePath, 'exts.properties') + if (extsFile.exists()) { + println "Processing extensions..." + processExtensions(extsFile, phpPrepPath) + } + + // Process PEAR if pear.properties exists + def pearFile = new File(bundlePath, 'pear.properties') + if (pearFile.exists()) { + println "Processing PEAR installation..." + processPear(pearFile, phpPrepPath) + } + + // Process dependencies if deps.properties exists + def depsFile = new File(bundlePath, 'deps.properties') + if (depsFile.exists()) { + println "Processing dependencies..." + processDependencies(depsFile, phpPrepPath) + } + + // Copy prepared files to bundles_build directory for persistence + def phpBuildPath = new File(bundleTmpBuildPath, bundleFolder) + println "" + println "Copying to build output directory..." + delete phpBuildPath + phpBuildPath.mkdirs() + copy { + from phpPrepPath + into phpBuildPath + } + println "Build output: ${phpBuildPath}" + + // Store the archive info for final message + // Note: Using extensions.extraProperties to avoid Task.project deprecation + extensions.extraProperties.set('preparedBundlePath', phpPrepPath.absolutePath) + extensions.extraProperties.set('buildOutputPath', phpBuildPath.absolutePath) + } +} + +// Task: Package release as archive (ensures the version folder is included at archive root) +// - Creates: bearsampp-build/bins/php//bearsampp-php--. +// - The archive contains the top-level folder: / ... +// Provider resolves version from either -PbundleVersion or value set by resolveVersion +def bundleVersionProvider = providers.provider { + def fromProp = project.findProperty('bundleVersion') as String + if (fromProp) return fromProp + // Read from file written by resolveVersion task + def propsFile = file("${buildTmpPath}/.gradle-bundleVersion") + if (propsFile.exists()) { + return propsFile.text.trim() + } + return null +} +def externalOutputDir = file("${moduleBuildOutputPath}") + +// Guard task: ensure bundleVersion is resolved before any packaging runs +tasks.register('assertVersionResolved') { + group = 'build' + description = 'Fail fast if bundleVersion was not resolved by resolveVersion' + dependsOn 'resolveVersion' + doLast { + def versionToBuild = bundleVersionProvider.getOrNull() + if (!versionToBuild) { + throw new GradleException("bundleVersion property not set. Run 'gradle resolveVersion' or invoke 'gradle release -PbundleVersion=<'*'|X.Y.Z>'") + } + } +} + +// 7z packager (uses ProcessBuilder pattern from MySQL for better control) +tasks.register('packageRelease7z') { + group = 'build' + description = 'Package release into a .7z archive (includes version folder at root)' + // Ensure version is resolved and preparation executed before packaging + dependsOn 'assertVersionResolved', 'releaseBuild' + + doLast { + def versionToBuild = bundleVersionProvider.getOrNull() + if (!versionToBuild) { + throw new GradleException("bundleVersion property not set") + } + + // Ensure prepared php directory exists + def bundleFolder = "${bundleName}${versionToBuild}" + def prepRoot = file("${bundleTmpPrepPath}") + def srcDir = new File(prepRoot, bundleFolder) + if (!srcDir.exists()) { + throw new GradleException("Prepared folder not found: ${srcDir}. Run releaseBuild first.") + } + + externalOutputDir.mkdirs() + def archiveName = "bearsampp-${bundleName}-${versionToBuild}-${bundleRelease}.7z" + def archiveFile = new File(externalOutputDir, archiveName) + + // Delete existing archive if present + if (archiveFile.exists()) { + delete archiveFile + } + + println "Packaging (7z): ${archiveFile}" + println "Included root folder: ${bundleFolder}/" + + // 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. + + Download from: https://www.7-zip.org/ + Or set 7Z_HOME to your 7-Zip installation directory. + """.stripIndent()) + } + + // Create 7z archive using ProcessBuilder (MySQL pattern) + // To ensure the archive contains the version folder (e.g., php8.3.15) at the root, + // we run 7-Zip from the parent directory and add the folder name explicitly. + def command = [ + sevenZipExe, + 'a', + '-t7z', + archiveFile.absolutePath.toString(), + bundleFolder + ] + + def process = new ProcessBuilder(command as String[]) + .directory(prepRoot) + .redirectErrorStream(true) + .start() + + process.inputStream.eachLine { line -> + if (line.trim()) println " ${line}" + } + + def exitCode = process.waitFor() + if (exitCode != 0) { + throw new GradleException("7-Zip compression failed with exit code: ${exitCode}") + } + + println "Archive created: ${archiveFile}" + } +} + +// Zip packager (Gradle native Zip task) — used if bundle.format != 7z +tasks.register('packageReleaseZip', Zip) { + group = 'build' + description = 'Package release into a .zip archive (includes version folder at root)' + // Ensure version is resolved and preparation executed before packaging + dependsOn 'assertVersionResolved', 'releaseBuild' + + doFirst { + def versionToBuild = bundleVersionProvider.getOrNull() + if (!versionToBuild) { + throw new GradleException("bundleVersion property not set") + } + + // Ensure prepared php directory exists + def bundleFolder = "${bundleName}${versionToBuild}" + def prepRoot = file("${bundleTmpPrepPath}") + def srcDir = new File(prepRoot, bundleFolder) + if (!srcDir.exists()) { + throw new GradleException("Prepared folder not found: ${srcDir}. Run releaseBuild first.") + } + + archiveFileName.set("bearsampp-${bundleName}-${versionToBuild}-${bundleRelease}.zip") + destinationDirectory.set(externalOutputDir) + + // Include the version folder as the top-level entry in the archive + from(prepRoot) { + include "${bundleFolder}/**" + } + + println "Packaging (zip): ${new File(externalOutputDir, archiveFileName.get()).absolutePath}" + println "Included root folder: ${bundleFolder}/" + } +} + +// Dispatcher task that chooses packager based on bundle.format (configured without executing tasks programmatically) +def archiveFormat = (bundleFormat ?: '7z').toLowerCase() +tasks.register('packageRelease') { + group = 'build' + description = 'Package release into archive (7z or zip) including the version folder at root' + // Ensure the preparation step runs before packaging + dependsOn 'resolveVersion', 'releaseBuild', 'assertVersionResolved' + // Select the concrete packager at configuration time based on build.properties + dependsOn archiveFormat == '7z' ? 'packageRelease7z' : 'packageReleaseZip' +} + +// Task: Generate hash files for the produced archive (.md5, .sha1, .sha256, .sha512) +tasks.register('generateHashes') { + group = 'build' + description = 'Generate hash sidecar files for the packaged archive' + + doLast { + def versionToBuild = bundleVersionProvider.getOrNull() + if (!versionToBuild) { + throw new GradleException("bundleVersion property not set") + } + def extFormat = (bundleFormat ?: '7z').toLowerCase() + def archive = new File(externalOutputDir, "bearsampp-${bundleName}-${versionToBuild}-${bundleRelease}.${extFormat}") + if (!archive.exists()) { + throw new GradleException("Archive not found for hashing: ${archive}") + } + + def calcHash = { File f, String algorithm -> + def digest = java.security.MessageDigest.getInstance(algorithm) + f.withInputStream { stream -> + byte[] buf = new byte[8192] + int r + while ((r = stream.read(buf)) != -1) { + digest.update(buf, 0, r) + } + } + digest.digest().collect { String.format('%02x', it) }.join('') + } + + def writeHash = { String algo, String ext -> + def h = calcHash(archive, algo) + def out = new File(archive.absolutePath + ".${ext}") + out.text = "${h} ${archive.name}\n" + println "Created: ${out.name}" + } + + writeHash('MD5', 'md5') + writeHash('SHA-1', 'sha1') + writeHash('SHA-256', 'sha256') + writeHash('SHA-512', 'sha512') + + // Print final success message + println "" + println "=".multiply(70) + println "[SUCCESS] Release build completed successfully for version ${versionToBuild}" + println "" + println "Build directories:" + println " Temp prep: ${file("${bundleTmpPrepPath}/${bundleName}${versionToBuild}").absolutePath}" + println " Build output: ${file("${bundleTmpBuildPath}/${bundleName}${versionToBuild}").absolutePath}" + println "" + println "Archive: ${new File(externalOutputDir, "bearsampp-${bundleName}-${versionToBuild}-${bundleRelease}").absolutePath}.${extFormat}" + println "=".multiply(70) + } +} + +// Ensure hashes are generated after packaging during release, and cleanup temp files +tasks.named('release') { + finalizedBy 'generateHashes', 'cleanupTempFiles' +} + +// Task: Cleanup temporary Gradle-specific files after build +tasks.register('cleanupTempFiles') { + group = 'build' + description = 'Cleanup temporary Gradle-specific files after build' + + doLast { + // Clean Gradle-specific temp files + def gradleBundleVersion = file("${buildTmpPath}/.gradle-bundleVersion") + if (gradleBundleVersion.exists()) { + delete gradleBundleVersion + println "Cleaned up: ${gradleBundleVersion.name}" + } + + def phpExtensionsTmp = file("${buildTmpPath}/php_extensions.tmp") + if (phpExtensionsTmp.exists()) { + delete phpExtensionsTmp + println "Cleaned up: ${phpExtensionsTmp.name}" + } + } +} + +// Helper: Process PHP extensions +def processExtensions(File extsFile, File phpPrepPath) { + def exts = new Properties() + extsFile.withInputStream { exts.load(it) } + + def extensionsText = new StringBuilder() + def tmpExtFile = file("${buildTmpPath}/php_extensions.tmp") + tmpExtFile.parentFile.mkdirs() + tmpExtFile.text = "" + + exts.each { key, url -> + def extName = key.toString().replace('phpexts.', '') + println " Processing extension: ${extName}" + + // Download extension + def downloadDir = file("${buildTmpPath}/downloads/ext/${extName}") + def extArchive = downloadFile(url.toString(), downloadDir) + + // Handle different download types + def mainDll = null + def extDir = downloadDir + + if (extArchive.name.endsWith('.dll')) { + // Direct DLL download - use it directly + mainDll = extArchive + } else if (extArchive.name.endsWith('.zip') || extArchive.name.endsWith('.7z')) { + // Archive - extract and find DLL + extDir = file("${downloadDir}/extracted") + extractArchive(extArchive, extDir) + + // Find the main DLL in extracted files + extDir.eachFileRecurse { file -> + if (file.name == "php_${extName}.dll") { + mainDll = file + } + } + } else { + throw new GradleException("Unsupported extension file type for ${extName}: ${extArchive.name}") + } + + if (!mainDll) { + throw new GradleException("Main DLL not found for extension: ${extName}. Downloaded file: ${extArchive.name}") + } + + // Validate architecture + println " Validating architecture..." + validateDllArchitecture(mainDll) + + // Copy extension DLL + def destDll = new File(phpPrepPath, "ext/php_${extName}.dll") + destDll.parentFile.mkdirs() + copy { + from mainDll + into destDll.parentFile + } + println " Copied: ${destDll.name}" + + // Handle special cases (e.g., imagick) + if (extName == 'imagick') { + def imagickDir = new File(phpPrepPath, 'imagick') + imagickDir.mkdirs() + copy { + from extDir + into imagickDir + include 'CORE_*.dll' + } + } + + // Add to extensions list (except xdebug) + if (extName != 'xdebug') { + extensionsText.append("extension=${extName}\n") + } + } + + // Update php.ini with extensions + def phpIniTemplate = new File(phpPrepPath.parentFile.parentFile.parentFile, "bin/${phpPrepPath.name}/php.ini") + if (phpIniTemplate.exists()) { + def phpIniContent = phpIniTemplate.text + phpIniContent = phpIniContent.replace('@PHP_EXTENSIONS@', extensionsText.toString()) + + def phpIniDest = new File(phpPrepPath, 'php.ini') + phpIniDest.text = phpIniContent + println " Updated php.ini with extensions" + } +} + +// Helper: Validate DLL architecture (uses ProcessBuilder instead of exec) +def validateDllArchitecture(File dll) { + def psCommand = """ +try { + \$bytes = [System.IO.File]::ReadAllBytes('${dll.absolutePath.replace('\\', '\\\\')}'); + \$peOffset = [System.BitConverter]::ToInt32(\$bytes, 60); + \$machineType = [System.BitConverter]::ToUInt16(\$bytes, \$peOffset + 4); + if (\$machineType -eq 0x8664) { + Write-Output '64-bit' + } elseif (\$machineType -eq 0x014c) { + Write-Output '32-bit' + } else { + Write-Output 'Unknown' + } +} catch { + Write-Output 'Error' +} +""".trim() + + def command = [ + 'powershell.exe', + '-NoLogo', + '-NoProfile', + '-Command', + psCommand + ] + + def process = new ProcessBuilder(command as String[]) + .redirectErrorStream(true) + .start() + + def output = new StringBuilder() + process.inputStream.eachLine { line -> + output.append(line).append('\n') + } + + def exitCode = process.waitFor() + def result = output.toString().trim() + + if (!result.contains('64-bit')) { + throw new GradleException("DLL ${dll.name} is not 64-bit architecture. Result: ${result}") + } +} + +// Helper: Process PEAR installation +def processPear(File pearFile, File phpPrepPath) { + def pear = new Properties() + pearFile.withInputStream { pear.load(it) } + + // Copy pear-install scripts + def pearInstallDir = new File(phpPrepPath, 'pear-install') + copy { + from pearInstallPath + into pearInstallDir + } + + // Download PEAR phar + def pearUrl = pear.getProperty('phppear.pear') + if (!pearUrl) { + println " [WARNING] PEAR URL not found in pear.properties (phppear.pear property is missing or empty)" + println " Skipping PEAR installation" + delete pearInstallDir + return + } + + def pearPhar = downloadFile(pearUrl, pearInstallDir) + + copy { + from pearPhar + into pearInstallDir + rename { 'install-pear-nozlib.phar' } + } + + // Execute PEAR installation + println " Installing PEAR..." + exec { + executable 'cmd' + args '/c', 'pear-install.bat' + workingDir pearInstallDir + } + + // Cleanup + delete pearInstallDir + println " PEAR installation completed" +} + +// Helper: Process dependencies +def processDependencies(File depsFile, File phpPrepPath) { + def deps = new Properties() + depsFile.withInputStream { deps.load(it) } + + deps.each { key, url -> + def depName = key.toString().replace('phpdeps.', '') + println " Processing dependency: ${depName}" + + // Download dependency + def downloadDir = file("${buildTmpPath}/downloads/dep/${depName}") + def depArchive = downloadFile(url.toString(), downloadDir) + + // Extract if needed + def depDir = downloadDir + if (depArchive.name.endsWith('.zip') || depArchive.name.endsWith('.7z')) { + depDir = file("${downloadDir}/extracted") + extractArchive(depArchive, depDir) + } + + // Handle special cases + if (depName == 'imagemagick') { + def imagickDir = new File(phpPrepPath, 'imagick') + imagickDir.mkdirs() + copy { + from depDir + into imagickDir + include '*.exe', '*.dll' + } + println " Copied ImageMagick files" + } + } +} + +// Task: List all bundle versions from releases.properties +tasks.register('listReleases') { + group = 'help' + description = 'List all available releases from releases.properties' + + doLast { + def releasesFile = file('releases.properties') + if (!releasesFile.exists()) { + println "releases.properties not found" + return + } + + def releases = new Properties() + releasesFile.withInputStream { releases.load(it) } + + println "\nAvailable PHP Releases:" + println "-".multiply(80) + releases.sort { it.key }.each { version, url -> + println " ${version.padRight(10)} -> ${url}" + } + println "-".multiply(80) + println "Total releases: ${releases.size()}" + } +} + +// Task: List available bundle versions in bin directory +tasks.register('listVersions') { + group = 'help' + description = 'List all available bundle versions in bin/ directory' + + doLast { + def binDir = file("${projectDir}/bin") + def archivedDir = file("${projectDir}/bin/archived") + + if (!binDir.exists()) { + println "bin/ directory not found" + return + } + + def inBin = binDir.listFiles() + ?.findAll { it.isDirectory() && it.name.startsWith(bundleName) && it.name != 'archived' } + ?.collect { it.name.replace(bundleName, '') } ?: [] + + def inArchived = archivedDir.exists() ? (archivedDir.listFiles() + ?.findAll { it.isDirectory() && it.name.startsWith(bundleName) } + ?.collect { it.name.replace(bundleName, '') } ?: []) : [] + + def allVersions = (inBin + inArchived).toSet().toList().sort { a, b -> + def pa = a.split('\\.').collect { it as int } + def pb = b.split('\\.').collect { it as int } + for (int i = 0; i < Math.max(pa.size(), pb.size()); i++) { + def ai = i < pa.size() ? pa[i] : 0 + def bi = i < pb.size() ? pb[i] : 0 + if (ai != bi) return ai <=> bi + } + return 0 + } + + println "\nAvailable ${bundleName} versions (index, version, location):" + println "-".multiply(60) + allVersions.eachWithIndex { v, idx -> + def tag + def inBinFlag = inBin.contains(v) + def inArchivedFlag = inArchived.contains(v) + if (inBinFlag && inArchivedFlag) { + tag = "[bin + bin/archived]" + } else if (inBinFlag) { + tag = "[bin]" + } else if (inArchivedFlag) { + tag = "[bin/archived]" + } else { + tag = "[unknown]" + } + def indexStr = String.format('%2d', idx + 1) + println " ${indexStr}. ${v.padRight(12)} ${tag}" + } + println "-".multiply(60) + println "Total versions: ${allVersions.size()}" + if (!allVersions.isEmpty()) { + println "\nTo build a specific version:" + println " gradle release -PbundleVersion=${allVersions.last()}" + } + } +} + +// Task: Validate build.properties +tasks.register('validateProperties') { + group = 'verification' + description = 'Validate build.properties configuration' + + doLast { + println "Validating build.properties..." + + def required = ['bundle.name', 'bundle.release', 'bundle.type', 'bundle.format'] + def missing = [] + + required.each { prop -> + if (!buildProps.containsKey(prop) || buildProps.getProperty(prop).trim().isEmpty()) { + missing.add(prop) + } + } + + if (missing.isEmpty()) { + println "[SUCCESS] All required properties are present:" + required.each { prop -> + println " ${prop} = ${buildProps.getProperty(prop)}" + } + } else { + println "[ERROR] Missing required properties:" + missing.each { prop -> + println " - ${prop}" + } + throw new GradleException("build.properties validation failed") + } + } +} + +// Task: List PHP extensions to be built +tasks.register('listExtensions') { + group = 'help' + description = 'List PHP extensions configured in bin directories' + + doLast { + def binDir = file("${projectDir}/bin") + if (!binDir.exists()) { + println "bin/ directory not found" + return + } + + println "\nScanning for PHP extension configurations..." + println "=".multiply(80) + + def foundExtensions = false + binDir.listFiles() + .findAll { it.isDirectory() && it.name.startsWith(bundleName) } + .sort { it.name } + .each { versionDir -> + def extsFile = new File(versionDir, 'exts.properties') + if (extsFile.exists()) { + foundExtensions = true + def version = versionDir.name.replace(bundleName, '') + println "\nPHP ${version}:" + println "-".multiply(80) + + def exts = new Properties() + extsFile.withInputStream { exts.load(it) } + + exts.sort { it.key }.each { ext, url -> + def extName = ext.toString().replace('phpexts.', '') + println " ${extName.padRight(20)} -> ${url}" + } + } + } + + if (!foundExtensions) { + println "No extension configurations found in bin/ directories" + } + println "=".multiply(80) + } +} + +// Task: List PEAR configurations +tasks.register('listPearConfig') { + group = 'help' + description = 'List PEAR configurations in bin directories' + + doLast { + def binDir = file("${projectDir}/bin") + if (!binDir.exists()) { + println "bin/ directory not found" + return + } + + println "\nScanning for PEAR configurations..." + println "=".multiply(80) + + def foundPear = false + binDir.listFiles() + .findAll { it.isDirectory() && it.name.startsWith(bundleName) } + .sort { it.name } + .each { versionDir -> + def pearFile = new File(versionDir, 'pear.properties') + if (pearFile.exists()) { + foundPear = true + def version = versionDir.name.replace(bundleName, '') + println "\nPHP ${version}:" + println "-".multiply(80) + + def pear = new Properties() + pearFile.withInputStream { pear.load(it) } + + pear.each { key, value -> + println " ${key.toString().padRight(20)} -> ${value}" + } + } + } + + if (!foundPear) { + println "No PEAR configurations found in bin/ directories" + } + println "=".multiply(80) + } +} + +// Task: List dependencies +tasks.register('listDependencies') { + group = 'help' + description = 'List dependencies configured in bin directories' + + doLast { + def binDir = file("${projectDir}/bin") + if (!binDir.exists()) { + println "bin/ directory not found" + return + } + + println "\nScanning for dependency configurations..." + println "=".multiply(80) + + def foundDeps = false + binDir.listFiles() + .findAll { it.isDirectory() && it.name.startsWith(bundleName) } + .sort { it.name } + .each { versionDir -> + def depsFile = new File(versionDir, 'deps.properties') + if (depsFile.exists()) { + foundDeps = true + def version = versionDir.name.replace(bundleName, '') + println "\nPHP ${version}:" + println "-".multiply(80) + + def deps = new Properties() + depsFile.withInputStream { deps.load(it) } + + deps.sort { it.key }.each { dep, url -> + def depName = dep.toString().replace('phpdeps.', '') + println " ${depName.padRight(20)} -> ${url}" + } + } + } + + if (!foundDeps) { + println "No dependency configurations found in bin/ directories" + } + println "=".multiply(80) + } +} + +// Task: Show PHP-specific build information +tasks.register('phpInfo') { + group = 'help' + description = 'Display PHP-specific build information' + + doLast { + println """ + ================================================================ + PHP Module Build - Specific Information + ================================================================ + + This module includes special build processes for: + + 1. PHP Extensions (exts.properties) + - Downloads and integrates PHP extensions + - Validates extensions with architecture check + - Automatically updates php.ini + + 2. PEAR Installation (pear.properties) + - Installs PEAR package manager + - Configures PEAR for the PHP version + + 3. Dependencies (deps.properties) + - Downloads required dependencies (e.g., ImageMagick) + - Integrates dependencies into the build + + 4. Architecture Verification + - Verifies digital signatures of extensions + - Ensures 64-bit compatibility + + Configuration Files: + - exts.properties : PHP extensions to include + - pear.properties : PEAR installation config + - deps.properties : External dependencies + + Useful Commands: + gradle listExtensions - Show configured extensions + gradle listPearConfig - Show PEAR configurations + gradle listDependencies - Show dependencies + gradle validateDllArchitecture -PdllFile= - Verify DLL + """.stripIndent() + } +} + +// ============================================================================ +// BUILD LIFECYCLE HOOKS +// ============================================================================ + +gradle.taskGraph.whenReady { graph -> + println """ + ================================================================ + Bearsampp Module PHP - Pure Gradle Build + ================================================================ + """.stripIndent() +} + +// ============================================================================ +// DEFAULT TASK +// ============================================================================ + +defaultTasks 'info' diff --git a/build.xml b/build.xml deleted file mode 100644 index fb02a259..00000000 --- a/build.xml +++ /dev/null @@ -1,222 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/current-repos.md b/current-repos.md deleted file mode 100644 index afbcd778..00000000 --- a/current-repos.md +++ /dev/null @@ -1,34 +0,0 @@ - -Bearsampp/aetraymenu -Bearsampp/Bearsampp -Bearsampp/dev -Bearsampp/login-servers-enhanced -Bearsampp/module-adminer -Bearsampp/module-apache -Bearsampp/module-composer -Bearsampp/module-consolez -Bearsampp/module-filezilla -Bearsampp/module-ghostscript -Bearsampp/module-git -Bearsampp/module-gitlist -Bearsampp/module-mailhog -Bearsampp/module-mariadb -Bearsampp/module-memcached -Bearsampp/module-mysql -Bearsampp/module-ngrok -Bearsampp/module-nodejs -Bearsampp/module-perl -Bearsampp/module-php -Bearsampp/module-phpmemadmin -Bearsampp/module-phpmyadmin -Bearsampp/module-phppgadmin -Bearsampp/module-postgresql -Bearsampp/module-python -Bearsampp/module-ruby -Bearsampp/module-svn -Bearsampp/module-webgrind -Bearsampp/module-websvn -Bearsampp/module-xdc -Bearsampp/module-yarn -Bearsampp/modules-untouched -Bearsampp/prerequisites \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 00000000..0ef5d470 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,19 @@ +# Gradle Build Properties for Bearsampp Module PHP + +# Gradle daemon configuration +org.gradle.daemon=true +org.gradle.parallel=true +org.gradle.caching=true + +# JVM settings for Gradle +org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError + +# Configure console output +org.gradle.console=auto +org.gradle.warning.mode=all + +# Build performance +org.gradle.configureondemand=false + +# Gradle version compatibility +# This project is compatible with Gradle 7.0+ diff --git a/module-php.RELEASE.launch b/module-php.RELEASE.launch deleted file mode 100644 index 956d826e..00000000 --- a/module-php.RELEASE.launch +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 00000000..0912540b --- /dev/null +++ b/settings.gradle @@ -0,0 +1,25 @@ +/* + * Bearsampp Module PHP - Gradle Settings + */ + +rootProject.name = 'module-php' + +// Enable Gradle features for better performance +enableFeaturePreview('STABLE_CONFIGURATION_CACHE') + +// Configure build cache for faster builds +buildCache { + local { + enabled = true + directory = file("${rootDir}/.gradle/build-cache") + } +} + +// Display initialization message +gradle.rootProject { + println """ + ================================================================ + Initializing Bearsampp Module PHP Build + ================================================================ + """.stripIndent() +} diff --git a/sigcheck.xml b/sigcheck.xml deleted file mode 100644 index 675eebb4..00000000 --- a/sigcheck.xml +++ /dev/null @@ -1,61 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/test.bat b/test.bat deleted file mode 100644 index d1cf04bc..00000000 --- a/test.bat +++ /dev/null @@ -1,4 +0,0 @@ -@echo off -php -v -pause -php -ri imagick