1+ name : Build and Release Python CWMS
2+
3+ on :
4+ push :
5+ tags :
6+ - ' v*'
7+ workflow_dispatch :
8+ inputs :
9+ version :
10+ description : ' Version number'
11+ required : true
12+ default : ' 0.8'
13+
14+ env :
15+ # WinPython Configuration - Update these variables as needed
16+ WINPYTHON_VERSION : " 16.6.20250620final"
17+ WINPYTHON_FILENAME : " Winpython64-3.12.10.1dot.zip"
18+ WINPYTHON_DOWNLOAD_URL : " https://github.com/winpython/winpython/releases/download/16.6.20250620final/Winpython64-3.12.10.1dot.zip"
19+
20+ # Alternative: You can also construct the URL from parts
21+ # WINPYTHON_BASE_URL: "https://github.com/winpython/winpython/releases/download"
22+ # Full URL would be: ${{ env.WINPYTHON_BASE_URL }}/${{ env.WINPYTHON_VERSION }}/${{ env.WINPYTHON_FILENAME }}
23+
24+ jobs :
25+ build :
26+ runs-on : windows-latest
27+
28+ steps :
29+ - name : Checkout repository
30+ uses : actions/checkout@v4
31+
32+ - name : Download WinPython
33+ run : |
34+ $url = "${{ env.WINPYTHON_DOWNLOAD_URL }}"
35+ $filename = "${{ env.WINPYTHON_FILENAME }}"
36+
37+ Write-Host "Downloading WinPython from: $url"
38+ Write-Host "Saving as: $filename"
39+
40+ Invoke-WebRequest -Uri $url -OutFile $filename
41+
42+ # Verify the file was downloaded
43+ if (Test-Path $filename) {
44+ $size = (Get-Item $filename).Length / 1MB
45+ Write-Host "✓ Downloaded successfully - Size: $([math]::Round($size, 2)) MB"
46+ } else {
47+ Write-Error "❌ Failed to download $filename"
48+ exit 1
49+ }
50+
51+ - name : Extract WinPython
52+ run : |
53+ $filename = "${{ env.WINPYTHON_FILENAME }}"
54+ Write-Host "Extracting $filename..."
55+
56+ Expand-Archive -Path $filename -DestinationPath "winpython_extracted"
57+
58+ # List what was extracted
59+ $extractedDirs = Get-ChildItem -Path "winpython_extracted" -Directory
60+ Write-Host "Extracted directories:"
61+ $extractedDirs | ForEach-Object { Write-Host " - $($_.Name)" }
62+
63+ - name : Install packages from requirements
64+ run : |
65+ $winpythonDir = Get-ChildItem -Path "winpython_extracted" -Directory | Select-Object -First 1
66+ Write-Host "Using WinPython directory: $($winpythonDir.Name)"
67+
68+ $pipPath = Join-Path $winpythonDir.FullName "python-*\Scripts\pip.exe"
69+ $pipExe = Get-ChildItem -Path $pipPath | Select-Object -First 1
70+
71+ if (Test-Path "requirements_binary_only.txt") {
72+ Write-Host "Installing packages from requirements_binary_only.txt..."
73+ & $pipExe.FullName install -r requirements_binary_only.txt --only-binary=all
74+ } else {
75+ Write-Host "No requirements_binary_only.txt found, skipping package installation"
76+ }
77+
78+ - name : Run setup script (if exists)
79+ run : |
80+ $winpythonDir = Get-ChildItem -Path "winpython_extracted" -Directory | Select-Object -First 1
81+ $pythonPath = Join-Path $winpythonDir.FullName "python-*\python.exe"
82+ $pythonExe = Get-ChildItem -Path $pythonPath | Select-Object -First 1
83+
84+ if (Test-Path "setup_environment.py") {
85+ Write-Host "Running setup_environment.py..."
86+ & $pythonExe.FullName setup_environment.py
87+ } else {
88+ Write-Host "No setup_environment.py found, skipping custom setup"
89+ }
90+
91+ - name : Create final directory structure
92+ run : |
93+ $winpythonDir = Get-ChildItem -Path "winpython_extracted" -Directory | Select-Object -First 1
94+ $version = if ($env:GITHUB_EVENT_NAME -eq "workflow_dispatch") {
95+ "${{ github.event.inputs.version }}"
96+ } else {
97+ "${{ github.ref_name }}" -replace "^v", ""
98+ }
99+ $finalDir = "pythonCWMS$version"
100+
101+ Write-Host "Creating final directory: $finalDir"
102+ Write-Host "Source WinPython directory: $($winpythonDir.Name)"
103+
104+ New-Item -ItemType Directory -Path $finalDir -Force
105+ Copy-Item -Path "$($winpythonDir.FullName)\*" -Destination $finalDir -Recurse -Force
106+
107+ # Copy any additional files from repo (excluding git files)
108+ Write-Host "Copying additional repository files..."
109+ Get-ChildItem -Path "." -Exclude ".git*", "winpython_extracted", "*.zip", $finalDir |
110+ ForEach-Object {
111+ Write-Host " Copying: $($_.Name)"
112+ Copy-Item -Path $_.FullName -Destination $finalDir -Recurse -Force
113+ }
114+
115+ echo "FINAL_DIR=$finalDir" >> $env:GITHUB_ENV
116+ echo "VERSION=$version" >> $env:GITHUB_ENV
117+
118+ # Store the WinPython subdirectory name for the config
119+ $winpythonSubDir = $winpythonDir.Name
120+ echo "WINPYTHON_SUBDIR=$winpythonSubDir" >> $env:GITHUB_ENV
121+ Write-Host "WinPython subdirectory: $winpythonSubDir"
122+
123+ - name : Install 7-Zip
124+ run : |
125+ Write-Host "Installing 7-Zip..."
126+ choco install 7zip -y
127+
128+ - name : Create 7z archive
129+ run : |
130+ $archiveName = "pythonCWMS${{ env.VERSION }}.7z"
131+ Write-Host "Creating archive: $archiveName"
132+ Write-Host "Compressing directory: ${{ env.FINAL_DIR }}"
133+
134+ & "C:\Program Files\7-Zip\7z.exe" a -t7z -mx=9 $archiveName "${{ env.FINAL_DIR }}"
135+
136+ if (Test-Path $archiveName) {
137+ $size = (Get-Item $archiveName).Length / 1MB
138+ Write-Host "✓ Archive created successfully - Size: $([math]::Round($size, 2)) MB"
139+ echo "ARCHIVE_SIZE_MB=$([math]::Round($size, 2))" >> $env:GITHUB_ENV
140+ } else {
141+ Write-Error "❌ Failed to create archive"
142+ exit 1
143+ }
144+
145+ echo "ARCHIVE_NAME=$archiveName" >> $env:GITHUB_ENV
146+
147+ - name : Calculate SHA256 hash
148+ run : |
149+ Write-Host "Calculating SHA256 hash for ${{ env.ARCHIVE_NAME }}..."
150+ $hash = Get-FileHash -Path "${{ env.ARCHIVE_NAME }}" -Algorithm SHA256
151+ $hashString = $hash.Hash
152+ echo "ARCHIVE_HASH=$hashString" >> $env:GITHUB_ENV
153+ Write-Host "✓ SHA256: $hashString"
154+
155+ - name : Generate config JSON
156+ run : |
157+ $repoOwner = "${{ github.repository_owner }}"
158+ $repoName = "${{ github.event.repository.name }}"
159+ $version = "${{ env.VERSION }}"
160+ $archiveName = "${{ env.ARCHIVE_NAME }}"
161+ $hash = "${{ env.ARCHIVE_HASH }}"
162+ $winpythonSubDir = "${{ env.WINPYTHON_SUBDIR }}"
163+ $winpythonVersion = "${{ env.WINPYTHON_VERSION }}"
164+ $winpythonFilename = "${{ env.WINPYTHON_FILENAME }}"
165+
166+ Write-Host "Generating configuration file..."
167+
168+ # Construct the download URL
169+ $downloadUrl = "https://github.com/$repoOwner/$repoName/releases/download/v$version/$archiveName"
170+
171+ # Find the python subdirectory (should be something like python-3.12.10.amd64)
172+ $pythonSubDir = Get-ChildItem -Path "${{ env.FINAL_DIR }}" -Directory -Name "python-*" | Select-Object -First 1
173+ $pythonExeSubDir = "$winpythonSubDir\$pythonSubDir"
174+
175+ Write-Host "Python executable subdirectory: $pythonExeSubDir"
176+
177+ # Create the config object
178+ $config = @{
179+ python_download_url = $downloadUrl
180+ python_expected_hash_sha256 = $hash
181+ default_install_directory = "C:\hec\python"
182+ default_env_var_name = "PYTHON_CWMS_HOME"
183+ python_exe_sub_directory = $pythonExeSubDir
184+ version = $version
185+ archive_filename = $archiveName
186+ archive_size_mb = [math]::Round((Get-Item "${{ env.ARCHIVE_NAME }}").Length / 1MB, 2)
187+ created_date = (Get-Date -Format "yyyy-MM-ddTHH:mm:ssZ")
188+ source_winpython_version = $winpythonVersion
189+ source_winpython_filename = $winpythonFilename
190+ }
191+
192+ # Convert to JSON and save
193+ $configJson = $config | ConvertTo-Json -Depth 10
194+ $configJson | Out-File -FilePath "pythonCWMS_config.json" -Encoding UTF8
195+
196+ Write-Host "✓ Generated config file:"
197+ Get-Content "pythonCWMS_config.json"
198+
199+ - name : Validate config file
200+ run : |
201+ Write-Host "Validating configuration file..."
202+ # Test that the JSON is valid
203+ try {
204+ $config = Get-Content "pythonCWMS_config.json" | ConvertFrom-Json
205+ Write-Host "✓ Config JSON is valid"
206+ Write-Host "✓ Download URL: $($config.python_download_url)"
207+ Write-Host "✓ Hash: $($config.python_expected_hash_sha256)"
208+ Write-Host "✓ Python exe path: $($config.python_exe_sub_directory)"
209+ Write-Host "✓ Source WinPython: $($config.source_winpython_filename)"
210+ }
211+ catch {
212+ Write-Error "❌ Config JSON is invalid: $_"
213+ exit 1
214+ }
215+
216+ - name : Create Release
217+ uses : softprops/action-gh-release@v1
218+ with :
219+ tag_name : ${{ github.ref_name || format('v{0}', github.event.inputs.version) }}
220+ name : Python CWMS ${{ env.VERSION }}
221+ draft : false
222+ prerelease : false
223+ files : |
224+ ${{ env.ARCHIVE_NAME }}
225+ pythonCWMS_config.json
226+ body : |
227+ ## Python CWMS ${{ env.VERSION }}
228+
229+ Portable Python environment with CWMS libraries and dependencies.
230+
231+ ### Downloads:
232+ - **`${{ env.ARCHIVE_NAME }}`** - Main Python environment archive (${{ env.ARCHIVE_SIZE_MB }} MB)
233+ - **`pythonCWMS_config.json`** - Configuration file for automated installers
234+
235+ ### Archive Details:
236+ - **Size:** ${{ env.ARCHIVE_SIZE_MB }} MB
237+ - **SHA256:** `${{ env.ARCHIVE_HASH }}`
238+ - **Source:** ${{ env.WINPYTHON_FILENAME }}
239+
240+ ### Contents:
241+ - WinPython 3.12.10.1 (from ${{ env.WINPYTHON_VERSION }})
242+ - Libraries from requirements_binary_only.txt
243+ - Custom setup and configuration
244+
245+ ### Usage:
246+ 1. Extract the .7z file to your desired location
247+ 2. Run `${{ env.FINAL_DIR }}\WinPython Command Prompt.exe`
248+ 3. Your environment is ready to use!
249+
250+ ### For Automated Installation:
251+ Use the `pythonCWMS_config.json` file with your installer scripts.
252+ env :
253+ GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
0 commit comments