setup-php 2.36.0 → 2.37.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +15 -12
  2. package/lib/config.js +5 -2
  3. package/lib/config.js.map +1 -1
  4. package/lib/core.d.ts +8 -0
  5. package/lib/core.js +55 -0
  6. package/lib/core.js.map +1 -0
  7. package/lib/extensions.js +9 -8
  8. package/lib/extensions.js.map +1 -1
  9. package/lib/fetch.js +25 -70
  10. package/lib/fetch.js.map +1 -1
  11. package/lib/install.js +3 -3
  12. package/lib/install.js.map +1 -1
  13. package/lib/tools.d.ts +50 -21
  14. package/lib/tools.js +211 -150
  15. package/lib/tools.js.map +1 -1
  16. package/lib/utils.d.ts +3 -0
  17. package/lib/utils.js +62 -26
  18. package/lib/utils.js.map +1 -1
  19. package/package.json +20 -20
  20. package/src/config.ts +5 -2
  21. package/src/configs/brew_extensions +19 -0
  22. package/src/configs/composer-gh-auth-no-op +3 -0
  23. package/src/configs/composer-gh-auth-warn +1 -0
  24. package/src/configs/os_releases.csv +4 -1
  25. package/src/core.ts +112 -0
  26. package/src/extensions.ts +16 -15
  27. package/src/fetch.ts +28 -42
  28. package/src/install.ts +6 -3
  29. package/src/scripts/darwin.sh +12 -6
  30. package/src/scripts/extensions/couchbase.sh +13 -2
  31. package/src/scripts/extensions/firebird.sh +6 -23
  32. package/src/scripts/extensions/gearman.sh +1 -1
  33. package/src/scripts/extensions/http.ps1 +7 -5
  34. package/src/scripts/extensions/phalcon.ps1 +3 -21
  35. package/src/scripts/extensions/phalcon.sh +2 -0
  36. package/src/scripts/extensions/relay.sh +5 -2
  37. package/src/scripts/extensions/source.sh +3 -1
  38. package/src/scripts/extensions/sqlsrv.ps1 +2 -0
  39. package/src/scripts/extensions/sqlsrv.sh +2 -0
  40. package/src/scripts/linux.sh +49 -9
  41. package/src/scripts/tools/add_tools.ps1 +75 -27
  42. package/src/scripts/tools/add_tools.sh +67 -23
  43. package/src/scripts/tools/blackfire.sh +1 -1
  44. package/src/scripts/tools/brew.sh +130 -0
  45. package/src/scripts/tools/grpc_php_plugin.sh +2 -2
  46. package/src/scripts/tools/ppa.sh +5 -1
  47. package/src/scripts/unix.sh +7 -3
  48. package/src/scripts/win32.ps1 +17 -11
  49. package/src/tools.ts +349 -203
  50. package/src/utils.ts +81 -34
@@ -3,6 +3,7 @@ $composer_home = "$env:APPDATA\Composer"
3
3
  $composer_bin = "$composer_home\vendor\bin"
4
4
  $composer_json = "$composer_home\composer.json"
5
5
  $composer_lock = "$composer_home\composer.lock"
6
+ $skip_composer_github_auth = $false
6
7
 
7
8
  # Function to configure composer.
8
9
  Function Edit-ComposerConfig() {
@@ -23,6 +24,7 @@ Function Edit-ComposerConfig() {
23
24
  if (-not(Test-Path $composer_json)) {
24
25
  Set-Content -Path $composer_json -Value "{}"
25
26
  }
27
+ Get-ToolVersion "composer" $null | Out-Null
26
28
  Set-ComposerEnv
27
29
  Add-Path $composer_bin
28
30
  Set-ComposerAuth
@@ -74,8 +76,18 @@ function Test-GitHubPublicAccess {
74
76
  }
75
77
  }
76
78
 
79
+ Function Write-ComposerGhAuthNoOpWarning() {
80
+ $message = (Get-Content (Join-Path $src 'configs\composer-gh-auth-warn') -Raw).Trim().Replace('%s', $composer_version)
81
+ if($env:fail_fast -eq 'true') {
82
+ Add-Log "$cross" "composer" $message
83
+ } else {
84
+ Write-Output "::warning::$message"
85
+ }
86
+ }
87
+
77
88
  # Function to setup authentication in composer.
78
89
  Function Set-ComposerAuth() {
90
+ $token = if ($env:COMPOSER_TOKEN) { $env:COMPOSER_TOKEN } else { $env:GITHUB_TOKEN }
79
91
  if(Test-Path env:COMPOSER_AUTH_JSON) {
80
92
  if(Test-Json -JSON $env:COMPOSER_AUTH_JSON) {
81
93
  Set-Content -Path $composer_home\auth.json -Value $env:COMPOSER_AUTH_JSON
@@ -83,13 +95,18 @@ Function Set-ComposerAuth() {
83
95
  Add-Log "$cross" "composer" "Could not parse COMPOSER_AUTH_JSON as valid JSON"
84
96
  }
85
97
  }
98
+ if($skip_composer_github_auth) {
99
+ Write-ComposerGhAuthNoOpWarning
100
+ }
86
101
  $composer_auth = @()
87
102
  if(Test-Path env:PACKAGIST_TOKEN) {
88
103
  $composer_auth += '"http-basic": {"repo.packagist.com": { "username": "token", "password": "' + $env:PACKAGIST_TOKEN + '"}}'
89
104
  }
90
105
  $write_token = $true
91
- $token = if ($env:COMPOSER_TOKEN) { $env:COMPOSER_TOKEN } else { $env:GITHUB_TOKEN }
92
106
  if ($token) {
107
+ if ($skip_composer_github_auth) {
108
+ $write_token = $false
109
+ }
93
110
  if ($env:GITHUB_SERVER_URL -ne "https://github.com" -and -not(Test-GitHubPublicAccess $token)) {
94
111
  $write_token = $false
95
112
  }
@@ -115,6 +132,18 @@ Function Set-ComposerEnv() {
115
132
  }
116
133
  }
117
134
 
135
+ # Function to identify latest-like URLs that should bypass the persistent cache.
136
+ Function Test-MutableToolUrl() {
137
+ Param(
138
+ [Parameter(Position = 0, Mandatory = $true)]
139
+ [string]
140
+ $Url
141
+ )
142
+ $mutableUrlRegex = '(^|[/?#._=-])(latest|stable|preview|snapshot|nightly|master)([/?#._=-]|$)|/releases/latest/download/'
143
+ $versionLikeRegex = '(^|[^0-9])[0-9]+\.[0-9]+([.-][0-9A-Za-z]+)*'
144
+ return ($Url -match $mutableUrlRegex) -or (($Url -match '\.phar([?#].*)?$') -and -not ($Url -match $versionLikeRegex))
145
+ }
146
+
118
147
  # Function to extract tool version.
119
148
  Function Get-ToolVersion() {
120
149
  Param (
@@ -158,12 +187,7 @@ Function Add-ToolsHelper() {
158
187
  } elseif($tool -eq "cs2pr") {
159
188
  (Get-Content $bin_dir/cs2pr).replace('exit(9)', 'exit(0)') | Set-Content $bin_dir/cs2pr
160
189
  } elseif($tool -eq "deployer") {
161
- if(Test-Path $composer_bin\deployer.phar.bat) {
162
- Copy-Item $composer_bin\deployer.phar.bat -Destination $composer_bin\dep.bat
163
- }
164
- if(Test-Path $composer_bin\dep.bat) {
165
- Copy-Item $composer_bin\dep.bat -Destination $composer_bin\deployer.bat
166
- }
190
+ Copy-Item $bin_dir\deployer.bat -Destination $bin_dir\dep.bat
167
191
  } elseif($tool -eq "phan") {
168
192
  $extensions += @('fileinfo', 'ast')
169
193
  } elseif($tool -eq "phinx") {
@@ -203,32 +227,58 @@ Function Add-Tool() {
203
227
  [ValidateNotNull()]
204
228
  $tool,
205
229
  [Parameter(Position = 2, Mandatory = $false)]
206
- $ver_param
230
+ $ver_param,
231
+ [Parameter(Position = 3, Mandatory = $false)]
232
+ $skip_composer_github_auth
207
233
  )
208
- if (Test-Path $bin_dir\$tool) {
209
- Copy-Item $bin_dir\$tool -Destination $bin_dir\$tool.old -Force
234
+ if($tool -eq "composer") {
235
+ $script:skip_composer_github_auth = $skip_composer_github_auth -eq 'true'
210
236
  }
237
+ $urls = $urls -split ','
211
238
  $tool_path = "$bin_dir\$tool"
212
- foreach ($url in $urls){
213
- if (($url | Split-Path -Extension) -eq ".exe") {
214
- $tool_path = "$tool_path.exe"
215
- }
216
- try {
217
- $status_code = (Invoke-WebRequest -Passthru -Uri $url -OutFile $tool_path).StatusCode
218
- } catch {
219
- if($url -match '.*github.com.*releases.*latest.*') {
220
- try {
221
- $url = $url.replace("releases/latest/download", "releases/download/" + ([regex]::match((Get-File -Url ($url.split('/release')[0] + "/releases")).Content, "([0-9]+\.[0-9]+\.[0-9]+)/" + ($url.Substring($url.LastIndexOf("/") + 1))).Groups[0].Value).split('/')[0])
222
- $status_code = (Invoke-WebRequest -Passthru -Uri $url -OutFile $tool_path).StatusCode
223
- } catch { }
239
+ $is_exe = ((($urls[0] | Split-Path -Extension).ToLowerInvariant()) -eq '.exe')
240
+ if ($is_exe) { $tool_path = "$tool_path.exe" }
241
+ $tool_ext = if ($is_exe) { '.exe' } else { '' }
242
+ $url_stream = [System.IO.MemoryStream]::New([System.Text.Encoding]::UTF8.GetBytes($urls[0]))
243
+ $cache_key = (Get-FileHash -InputStream $url_stream -Algorithm SHA256).Hash.Substring(0, 16)
244
+ $cache_path = "$env:TEMP\$tool-$cache_key$tool_ext"
245
+ $use_cache = -not (Test-MutableToolUrl $urls[0])
246
+ $status_code = 200
247
+ if ($use_cache -and (Test-Path $cache_path -PathType Leaf)) {
248
+ Copy-Item $cache_path -Destination $tool_path -Force
249
+ } else {
250
+ $backup_path = "$tool_path.bak"
251
+ if (Test-Path $tool_path) { Copy-Item $tool_path -Destination $backup_path -Force }
252
+ foreach ($url in $urls){
253
+ try {
254
+ $status_code = (Invoke-WebRequest -Passthru -Uri $url -OutFile $tool_path).StatusCode
255
+ } catch {
256
+ if($url -match '.*github.com.*releases.*latest.*') {
257
+ try {
258
+ $url = $url.replace("releases/latest/download", "releases/download/" + ([regex]::match((Get-File -Url ($url.split('/release')[0] + "/releases")).Content, "([0-9]+\.[0-9]+\.[0-9]+)/" + ($url.Substring($url.LastIndexOf("/") + 1))).Groups[0].Value).split('/')[0])
259
+ $status_code = (Invoke-WebRequest -Passthru -Uri $url -OutFile $tool_path).StatusCode
260
+ } catch {
261
+ $status_code = 0
262
+ }
263
+ } else {
264
+ $status_code = 0
265
+ }
266
+ }
267
+ if($status_code -eq 200 -and (Test-Path $tool_path)) {
268
+ if ($use_cache) {
269
+ Copy-Item $tool_path -Destination $cache_path -Force
270
+ }
271
+ break
224
272
  }
225
273
  }
226
- if($status_code -eq 200 -and (Test-Path $tool_path)) {
227
- break
274
+ if ($status_code -ne 200 -and (Test-Path $backup_path)) {
275
+ Copy-Item $backup_path -Destination $tool_path -Force
228
276
  }
277
+ Remove-Item $backup_path -Force -ErrorAction SilentlyContinue
229
278
  }
230
279
 
231
- if (((Get-ChildItem -Path $bin_dir/* | Where-Object Name -Match "^$tool(.exe|.phar)*$").Count -gt 0)) {
280
+ $escaped_tool = [regex]::Escape($tool)
281
+ if (((Get-ChildItem -Path $bin_dir/* | Where-Object Name -Match "^$escaped_tool(\.exe|\.phar)?$").Count -gt 0)) {
232
282
  $bat_content = @()
233
283
  $bat_content += "@ECHO off"
234
284
  $bat_content += "setlocal DISABLEDELAYEDEXPANSION"
@@ -242,8 +292,6 @@ Function Add-Tool() {
242
292
  } else {
243
293
  if($tool -eq "composer") {
244
294
  $env:fail_fast = 'true'
245
- } elseif (Test-Path $bin_dir\$tool.old) {
246
- Copy-Item $bin_dir\$tool.old -Destination $bin_dir\$tool -Force
247
295
  }
248
296
  Add-Log $cross $tool "Could not add $tool"
249
297
  }
@@ -3,6 +3,7 @@ export composer_home="$HOME/.composer"
3
3
  export composer_bin="$composer_home/vendor/bin"
4
4
  export composer_json="$composer_home/composer.json"
5
5
  export composer_lock="$composer_home/composer.lock"
6
+ skip_composer_github_auth=false
6
7
 
7
8
  # Function to extract tool version.
8
9
  get_tool_version() {
@@ -41,6 +42,7 @@ configure_composer() {
41
42
  echo '{}' | tee "$composer_json" >/dev/null
42
43
  chmod 644 "$composer_json"
43
44
  fi
45
+ get_tool_version composer >/dev/null
44
46
  set_composer_env
45
47
  add_path "$composer_bin"
46
48
  set_composer_auth
@@ -70,22 +72,39 @@ can_access_public_github() {
70
72
  curl --fail -s -H "Authorization: token $1" 'https://api.github.com/' >/dev/null 2>&1
71
73
  }
72
74
 
75
+ composer_gh_auth_no_op() {
76
+ local message
77
+ message="$(<"${src:?}"/configs/composer-gh-auth-warn)"
78
+ message="${message//%s/$composer_version}"
79
+ if [ "${fail_fast:-false}" = "true" ]; then
80
+ add_log "${cross:?}" "composer" "$message"
81
+ else
82
+ echo "::warning::$message"
83
+ fi
84
+ }
85
+
73
86
  # Function to setup authentication in composer.
74
87
  set_composer_auth() {
75
- if [ -n "$COMPOSER_AUTH_JSON" ]; then
76
- if php -r "json_decode('$COMPOSER_AUTH_JSON'); if(json_last_error() !== JSON_ERROR_NONE) { throw new Exception('invalid json'); }"; then
77
- echo "$COMPOSER_AUTH_JSON" | tee "$composer_home/auth.json" >/dev/null
88
+ token="${COMPOSER_TOKEN:-$GITHUB_TOKEN}"
89
+ if [ -n "${COMPOSER_AUTH_JSON:-}" ]; then
90
+ if printf '%s' "$COMPOSER_AUTH_JSON" | jq -e . >/dev/null; then
91
+ printf '%s' "$COMPOSER_AUTH_JSON" | tee "$composer_home/auth.json" >/dev/null
78
92
  else
79
93
  add_log "${cross:?}" "composer" "Could not parse COMPOSER_AUTH_JSON as valid JSON"
80
94
  fi
81
95
  fi
96
+ if [ "$skip_composer_github_auth" = "true" ]; then
97
+ composer_gh_auth_no_op
98
+ fi
82
99
  composer_auth=()
83
100
  if [ -n "$PACKAGIST_TOKEN" ]; then
84
101
  composer_auth+=( '"http-basic": {"repo.packagist.com": { "username": "token", "password": "'"$PACKAGIST_TOKEN"'"}}' )
85
102
  fi
86
- token="${COMPOSER_TOKEN:-$GITHUB_TOKEN}"
87
103
  if [ -n "$token" ]; then
88
104
  write_token=true
105
+ if [ "$skip_composer_github_auth" = "true" ]; then
106
+ write_token=false
107
+ fi
89
108
  if [ "$GITHUB_SERVER_URL" != "https://github.com" ]; then
90
109
  can_access_public_github "$token" || write_token=false
91
110
  fi
@@ -113,6 +132,16 @@ set_composer_env() {
113
132
  fi
114
133
  }
115
134
 
135
+ # Function to identify latest-like URLs that should bypass the persistent cache.
136
+ is_mutable_tool_url() {
137
+ local tool_url=$1
138
+ local mutable_url_regex='(^|[/?#._=-])(latest|stable|preview|snapshot|nightly|master)([/?#._=-]|$)|/releases/latest/download/'
139
+ local version_like_regex='(^|[^0-9])[0-9]+\.[0-9]+([.-][0-9A-Za-z]+)*'
140
+ [[ "$tool_url" =~ $mutable_url_regex ]] && return 0
141
+ [[ "$tool_url" =~ \.phar([?#].*)?$ && ! "$tool_url" =~ $version_like_regex ]] && return 0
142
+ return 1
143
+ }
144
+
116
145
  # Helper function to configure tools.
117
146
  add_tools_helper() {
118
147
  tool=$1
@@ -123,19 +152,15 @@ add_tools_helper() {
123
152
  extensions+=(iconv mbstring phar sodium)
124
153
  elif [ "$tool" = "codeception" ]; then
125
154
  extensions+=(json mbstring)
126
- sudo ln -s "$scoped_dir"/vendor/bin/codecept "$scoped_dir"/vendor/bin/codeception
155
+ sudo ln -s "$scoped_dir"/vendor/bin/codecept "$scoped_dir"/vendor/bin/codeception 2>/dev/null || true
127
156
  elif [ "$tool" = "composer" ]; then
128
157
  configure_composer "$tool_path"
129
158
  elif [ "$tool" = "cs2pr" ]; then
130
159
  sudo sed -i 's/\r$//; s/exit(9)/exit(0)/' "$tool_path" 2>/dev/null ||
131
160
  sudo sed -i '' 's/\r$//; s/exit(9)/exit(0)/' "$tool_path"
132
161
  elif [ "$tool" = "deployer" ]; then
133
- if [ -e "$composer_bin"/deployer.phar ]; then
134
- sudo ln -s "$composer_bin"/deployer.phar "$composer_bin"/dep
135
- fi
136
- if [ -e "$composer_bin"/dep ]; then
137
- sudo ln -s "$composer_bin"/dep "$composer_bin"/deployer
138
- fi
162
+ sudo ln -s "$tool_path" "$tool_path_dir"/deployer 2>/dev/null || true
163
+ sudo ln -s "$tool_path" "$tool_path_dir"/dep 2>/dev/null || true
139
164
  elif [ "$tool" = "phan" ]; then
140
165
  extensions+=(fileinfo ast)
141
166
  elif [ "$tool" = "phinx" ]; then
@@ -151,7 +176,7 @@ add_tools_helper() {
151
176
  elif [ "$tool" = "phpDocumentor" ]; then
152
177
  extensions+=(ctype hash json fileinfo iconv mbstring simplexml xml)
153
178
  sudo ln -s "$tool_path" "$tool_path_dir"/phpdocumentor 2>/dev/null || true
154
- sudo ln -s "$tool_path" "$tool_path_dir"/phpdoc
179
+ sudo ln -s "$tool_path" "$tool_path_dir"/phpdoc 2>/dev/null || true
155
180
  elif [ "$tool" = "phpunit" ]; then
156
181
  extensions+=(dom json libxml mbstring xml xmlwriter)
157
182
  elif [ "$tool" = "phpunit-bridge" ]; then
@@ -162,9 +187,9 @@ add_tools_helper() {
162
187
  fi
163
188
  elif [ "$tool" = "vapor-cli" ]; then
164
189
  extensions+=(fileinfo json mbstring zip simplexml)
165
- sudo ln -s "$scoped_dir"/vendor/bin/vapor "$scoped_dir"/vendor/bin/vapor-cli
190
+ sudo ln -s "$scoped_dir"/vendor/bin/vapor "$scoped_dir"/vendor/bin/vapor-cli 2>/dev/null || true
166
191
  elif [ "$tool" = wp-cli ]; then
167
- sudo ln -s "$tool_path" "$tool_path_dir"/"${tool%-*}"
192
+ sudo ln -s "$tool_path" "$tool_path_dir"/"${tool%-*}" 2>/dev/null || true
168
193
  fi
169
194
  for extension in "${extensions[@]}"; do
170
195
  add_extension "$extension" extension >/dev/null 2>&1
@@ -176,29 +201,48 @@ add_tool() {
176
201
  url=$1
177
202
  tool=$2
178
203
  ver_param=$3
204
+ if [ "$tool" = "composer" ]; then
205
+ skip_composer_github_auth="${4:-false}"
206
+ fi
179
207
  tool_path="$tool_path_dir/$tool"
180
208
  if ! [ -d "$tool_path_dir" ]; then
181
209
  sudo mkdir -p "$tool_path_dir"
182
210
  fi
183
- add_path "$tool_path_dir"
184
- if [ -e "$tool_path" ]; then
185
- sudo cp -aL "$tool_path" /tmp/"$tool"
211
+ if ! [ -d "$tool_cache_path_dir" ]; then
212
+ sudo mkdir -p "$tool_cache_path_dir"
186
213
  fi
214
+ add_path "$tool_path_dir" verify
215
+ add_path "$tool_cache_path_dir"
187
216
  IFS="," read -r -a url <<<"$url"
188
- status_code=$(get -v -e "$tool_path" "${url[@]}")
189
- if [ "$status_code" != "200" ] && [[ "${url[0]}" =~ .*github.com.*releases.*latest.* ]]; then
190
- url[0]="${url[0]//releases\/latest\/download/releases/download/$(get -s -n "" "$(echo "${url[0]}" | cut -d '/' -f '1-5')/releases" | grep -Eo -m 1 "([0-9]+\.[0-9]+\.[0-9]+)/$(echo "${url[0]}" | sed -e "s/.*\///")" | cut -d '/' -f 1)}"
191
- status_code=$(get -v -e "$tool_path" "${url[0]}")
217
+ cache_key=$(get_sha256 "${url[0]}" | head -c 16)
218
+ cache_path="$tool_cache_path_dir/${tool}-${cache_key}"
219
+ use_cache=true
220
+ is_mutable_tool_url "${url[0]}" && use_cache=false
221
+ status_code="200"
222
+ if [ "$use_cache" = "true" ] && [ -f "$cache_path" ]; then
223
+ sudo cp -a "$cache_path" "$tool_path"
224
+ else
225
+ [ -f "$tool_path" ] && sudo cp -a "$tool_path" "$tool_path.bak"
226
+ status_code=$(get -v -e "$tool_path" "${url[@]}")
227
+ if [ "$status_code" != "200" ] && [[ "${url[0]}" =~ .*github.com.*releases.*latest.* ]]; then
228
+ url[0]="${url[0]//releases\/latest\/download/releases/download/$(get -s -n "" "$(echo "${url[0]}" | cut -d '/' -f '1-5')/releases" | grep -Eo -m 1 "([0-9]+\.[0-9]+\.[0-9]+)/$(echo "${url[0]}" | sed -e "s/.*\///")" | cut -d '/' -f 1)}"
229
+ status_code=$(get -v -e "$tool_path" "${url[0]}")
230
+ fi
231
+ if [ "$status_code" = "200" ]; then
232
+ [ "$use_cache" = "true" ] && sudo cp -a "$tool_path" "$cache_path"
233
+ elif [ -f "$tool_path.bak" ]; then
234
+ sudo mv "$tool_path.bak" "$tool_path"
235
+ fi
236
+ sudo rm -f "$tool_path.bak"
192
237
  fi
193
238
  if [ "$status_code" = "200" ]; then
194
239
  add_tools_helper "$tool"
195
240
  tool_version=$(get_tool_version "$tool" "$ver_param")
241
+ sudo ln -sfn "$tool_path" "$tool_cache_path_dir/$tool" 2>/dev/null || true
196
242
  add_log "${tick:?}" "$tool" "Added $tool $tool_version"
197
243
  else
198
244
  if [ "$tool" = "composer" ]; then
199
245
  export fail_fast=true
200
- elif [ -e /tmp/"$tool" ]; then
201
- sudo cp -a /tmp/"$tool" "$tool_path"
202
246
  fi
203
247
  if [ "$status_code" = "404" ]; then
204
248
  add_log "$cross" "$tool" "Failed to download $tool from ${url[*]}"
@@ -8,7 +8,7 @@ add_blackfire_linux() {
8
8
  add_blackfire_darwin() {
9
9
  sudo mkdir -p /usr/local/var/run
10
10
  add_brew_tap blackfireio/homebrew-blackfire
11
- brew install blackfire
11
+ safe_brew install blackfire
12
12
  }
13
13
 
14
14
  blackfire_config() {
@@ -44,6 +44,135 @@ add_brew_bins_to_path() {
44
44
  add_path "$brew_prefix"/sbin
45
45
  }
46
46
 
47
+ # Function to get file modification time.
48
+ get_file_mtime() {
49
+ local file=$1
50
+ if [ "$(uname -s)" = "Darwin" ]; then
51
+ stat -f "%m" "$file" 2>/dev/null || echo 0
52
+ else
53
+ stat -c "%Y" "$file" 2>/dev/null || echo 0
54
+ fi
55
+ }
56
+
57
+ # Function to terminate a process and its direct children.
58
+ terminate_process_tree() {
59
+ local pid=$1
60
+ local children child
61
+ children=$(pgrep -P "$pid" 2>/dev/null || true)
62
+ kill -TERM "$pid" >/dev/null 2>&1 || true
63
+ for child in $children; do
64
+ terminate_process_tree "$child"
65
+ done
66
+ sleep 2
67
+ kill -KILL "$pid" >/dev/null 2>&1 || true
68
+ for child in $children; do
69
+ terminate_process_tree "$child"
70
+ done
71
+ }
72
+
73
+ # Function to run a command with an inactivity watchdog.
74
+ run_with_inactivity_watchdog() {
75
+ local timeout_secs="${SETUP_PHP_BREW_INACTIVITY_TIMEOUT:-180}"
76
+ local poll_secs="${SETUP_PHP_BREW_WATCHDOG_POLL:-5}"
77
+ local tmp_dir stdout_fifo stderr_fifo stdout_log stderr_log timeout_file
78
+ local command_pid stdout_reader_pid stderr_reader_pid monitor_pid exit_code
79
+ tmp_dir="$(mktemp -d "${TMPDIR:-/tmp}/setup-php-brew.XXXXXX")" || return 1
80
+ stdout_fifo="$tmp_dir/stdout.fifo"
81
+ stderr_fifo="$tmp_dir/stderr.fifo"
82
+ stdout_log="$tmp_dir/stdout.log"
83
+ stderr_log="$tmp_dir/stderr.log"
84
+ timeout_file="$tmp_dir/timed_out"
85
+ mkfifo "$stdout_fifo" "$stderr_fifo" || {
86
+ rm -rf "$tmp_dir"
87
+ return 1
88
+ }
89
+ : >"$stdout_log"
90
+ : >"$stderr_log"
91
+
92
+ ("$@" >"$stdout_fifo" 2>"$stderr_fifo") &
93
+ command_pid=$!
94
+
95
+ (
96
+ while IFS= read -r line || [ -n "$line" ]; do
97
+ printf '%s\n' "$line"
98
+ printf '%s\n' "$line" >>"$stdout_log"
99
+ done <"$stdout_fifo"
100
+ ) &
101
+ stdout_reader_pid=$!
102
+
103
+ (
104
+ while IFS= read -r line || [ -n "$line" ]; do
105
+ printf '%s\n' "$line" >&2
106
+ printf '%s\n' "$line" >>"$stderr_log"
107
+ done <"$stderr_fifo"
108
+ ) &
109
+ stderr_reader_pid=$!
110
+
111
+ (
112
+ local last_activity current_activity current_err_activity now
113
+ last_activity=$(get_file_mtime "$stdout_log")
114
+ current_err_activity=$(get_file_mtime "$stderr_log")
115
+ [ "$current_err_activity" -gt "$last_activity" ] && last_activity="$current_err_activity"
116
+ while kill -0 "$command_pid" >/dev/null 2>&1; do
117
+ sleep "$poll_secs"
118
+ current_activity=$(get_file_mtime "$stdout_log")
119
+ [ "$current_activity" -gt "$last_activity" ] && last_activity="$current_activity"
120
+ current_err_activity=$(get_file_mtime "$stderr_log")
121
+ [ "$current_err_activity" -gt "$last_activity" ] && last_activity="$current_err_activity"
122
+ now=$(date +%s)
123
+ if [ $((now - last_activity)) -ge "$timeout_secs" ]; then
124
+ printf "\nsetup-php: brew produced no output for %ss; terminating and retrying...\n" "$timeout_secs" >&2
125
+ : >"$timeout_file"
126
+ terminate_process_tree "$command_pid"
127
+ break
128
+ fi
129
+ done
130
+ ) &
131
+ monitor_pid=$!
132
+
133
+ wait "$command_pid"
134
+ exit_code=$?
135
+ wait "$stdout_reader_pid" 2>/dev/null || true
136
+ wait "$stderr_reader_pid" 2>/dev/null || true
137
+ kill "$monitor_pid" >/dev/null 2>&1 || true
138
+ wait "$monitor_pid" 2>/dev/null || true
139
+
140
+ if [ -e "$timeout_file" ]; then
141
+ rm -rf "$tmp_dir"
142
+ return 124
143
+ fi
144
+
145
+ rm -rf "$tmp_dir"
146
+ return "$exit_code"
147
+ }
148
+
149
+ # Function to run brew with retries and an inactivity watchdog.
150
+ safe_brew() {
151
+ local max_attempts="${SETUP_PHP_BREW_RETRY_ATTEMPTS:-3}"
152
+ local attempt=1
153
+ local exit_code=0
154
+
155
+ if [ "${SETUP_PHP_BREW_WATCHDOG:-true}" = "false" ]; then
156
+ brew "$@"
157
+ return $?
158
+ fi
159
+
160
+ while [ "$attempt" -le "$max_attempts" ]; do
161
+ run_with_inactivity_watchdog brew "$@" && return 0
162
+ exit_code=$?
163
+
164
+ if [ "$attempt" -ge "$max_attempts" ]; then
165
+ return "$exit_code"
166
+ fi
167
+
168
+ printf "setup-php: retrying brew command (attempt %s/%s, exit %s)\n" "$((attempt + 1))" "$max_attempts" "$exit_code" >&2
169
+ sleep "$((attempt * 5))"
170
+ attempt=$((attempt + 1))
171
+ done
172
+
173
+ return "$exit_code"
174
+ }
175
+
47
176
  # Function to add brew.
48
177
  add_brew() {
49
178
  brew_prefix="$(get_brew_prefix)"
@@ -74,6 +203,7 @@ configure_brew() {
74
203
  export HOMEBREW_NO_ENV_HINTS=1
75
204
  export HOMEBREW_NO_INSTALL_CLEANUP=1
76
205
  export HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1
206
+ export HOMEBREW_DOWNLOAD_CONCURRENCY="${HOMEBREW_DOWNLOAD_CONCURRENCY:-6}"
77
207
  export brew_opts
78
208
  export brew_path
79
209
  export brew_path_dir
@@ -4,7 +4,7 @@ add_bazel() {
4
4
  add_list bazel/apt https://storage.googleapis.com/bazel-apt https://bazel.build/bazel-release.pub.gpg stable jdk1.8
5
5
  install_packages bazel
6
6
  else
7
- brew install bazel
7
+ safe_brew install bazel
8
8
  fi
9
9
  fi
10
10
  }
@@ -25,7 +25,7 @@ add_grpc_php_plugin_brew() {
25
25
  . "${0%/*}"/tools/brew.sh
26
26
  configure_brew
27
27
  [ -e /usr/local/bin/protoc ] && sudo mv /usr/local/bin/protoc /tmp/protoc && sudo mv /usr/local/include/google /tmp
28
- brew install grpc
28
+ safe_brew install grpc
29
29
  brew link --force --overwrite grpc >/dev/null 2>&1
30
30
  [ -e /tmp/protoc ] && sudo mv /tmp/protoc /usr/local/bin/protoc && sudo mv /tmp/google /usr/local/include/
31
31
  grpc_tag="v$(brew info grpc | grep "grpc:" | grep -Eo "[0-9]+\.[0-9]+\.[0-9]+")"
@@ -246,7 +246,11 @@ check_lists() {
246
246
  match_file=$(grep -Elr "$primary" "$list_dir" 2>/dev/null | head -n 1)
247
247
  fi
248
248
  if [ -z "$match_file" ] && [ -n "$secondary" ]; then
249
- match_file=$(grep -Elr "$secondary" "$list_dir" 2>/dev/null | head -n 1)
249
+ local candidate
250
+ candidate=$(grep -Elr "$secondary" "$list_dir" 2>/dev/null | head -n 1)
251
+ if [ -n "$candidate" ] && { [ -z "$primary" ] || grep -Eq "$primary" "$candidate"; }; then
252
+ match_file="$candidate"
253
+ fi
250
254
  fi
251
255
  if [ -n "$match_file" ]; then
252
256
  local list_count
@@ -4,6 +4,7 @@ export cross="✗"
4
4
  export curl_opts=(-sL)
5
5
  export old_versions="5.[3-5]"
6
6
  export jit_versions="8.[0-9]"
7
+ export php_builder_versions="8.[3-9]"
7
8
  export nightly_versions="8.[6-9]"
8
9
  export xdebug3_versions="7.[2-4]|8.[0-9]"
9
10
  export latest="releases/latest/download"
@@ -58,6 +59,7 @@ read_env() {
58
59
  -n "$ACT" || -n "$CONTAINER" ]] && _runner=self-hosted || _runner=github
59
60
  runner="${runner:-${RUNNER:-$_runner}}"
60
61
  tool_path_dir="${setup_php_tools_dir:-${SETUP_PHP_TOOLS_DIR:-/usr/local/bin}}"
62
+ tool_cache_path_dir="${setup_php_tool_cache_dir:-${SETUP_PHP_TOOL_CACHE_DIR:-${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}/setup-php/tools}}"
61
63
 
62
64
  if [[ "$runner" = "github" && $_runner = "self-hosted" ]]; then
63
65
  fail_fast=true
@@ -79,6 +81,7 @@ read_env() {
79
81
  export update
80
82
  export ts
81
83
  export tool_path_dir
84
+ export tool_cache_path_dir
82
85
  }
83
86
 
84
87
  # Function to create a lock.
@@ -169,14 +172,15 @@ get_shell_profile() {
169
172
  # Function to add a path to the PATH variable.
170
173
  add_path() {
171
174
  path_to_add=$1
172
- [[ ":$PATH:" == *":$path_to_add:"* ]] && return
175
+ action=$2
176
+ [[ "$action" == "verify" && ":$PATH:" == *":$path_to_add:"* ]] && return
173
177
  if [[ -n "$GITHUB_PATH" ]]; then
174
- echo "$path_to_add" | tee -a "$GITHUB_PATH" >/dev/null 2>&1
178
+ printf '%s\n%s' "$path_to_add" "$(grep -v "^${path_to_add}$" "$GITHUB_PATH" 2>/dev/null)" > "$GITHUB_PATH"
175
179
  else
176
180
  profile=$(get_shell_profile)
177
181
  ([ -e "$profile" ] && grep -q ":$path_to_add\"" "$profile" 2>/dev/null) || echo "export PATH=\"\${PATH:+\${PATH}:}\"$path_to_add" | sudo tee -a "$profile" >/dev/null 2>&1
178
182
  fi
179
- export PATH="${PATH:+${PATH}:}$path_to_add"
183
+ [[ ":$PATH:" == *":$path_to_add:"* ]] || export PATH="${PATH:+${PATH}:}$path_to_add"
180
184
  }
181
185
 
182
186
  # Function to add environment variables using a PATH.
@@ -81,9 +81,10 @@ Function Get-PathFromRegistry {
81
81
  # Function to add a location to PATH.
82
82
  Function Add-Path {
83
83
  param(
84
- [string]$PathItem
84
+ [string]$PathItem,
85
+ [switch]$Force
85
86
  )
86
- if("$env:PATH;".contains("$PathItem;")) {
87
+ if(-not($Force) -and "$env:PATH;".contains("$PathItem;")) {
87
88
  return
88
89
  }
89
90
  if ($env:GITHUB_PATH) {
@@ -202,16 +203,20 @@ Function Install-PSPackage() {
202
203
  $cmdlet
203
204
  )
204
205
  $module_path = "$bin_dir\$psm1_path.psm1"
205
- if(-not (Test-Path $module_path -PathType Leaf)) {
206
- $zip_file = "$bin_dir\$package.zip"
207
- Get-File -Url $url -OutFile $zip_file
208
- Expand-Archive -Path $zip_file -DestinationPath $bin_dir -Force
209
- }
210
- Import-Module $module_path
211
- if($null -eq (Get-Command $cmdlet -ErrorAction SilentlyContinue)) {
212
- Install-Module -Name $package -Force
213
- } else {
206
+ $imported = $false
207
+ try {
208
+ if(-not (Test-Path $module_path -PathType Leaf)) {
209
+ $zip_file = "$bin_dir\$package.zip"
210
+ Get-File -Url $url -OutFile $zip_file
211
+ Expand-Archive -Path $zip_file -DestinationPath $bin_dir -Force -ErrorAction Stop
212
+ }
213
+ Import-Module $module_path -ErrorAction Stop
214
+ $imported = $null -ne (Get-Command $cmdlet -ErrorAction SilentlyContinue)
215
+ } catch { }
216
+ if($imported) {
214
217
  Add-ToProfile $current_profile "$package-search" "Import-Module $module_path"
218
+ } else {
219
+ Install-Module -Name $package -Force
215
220
  }
216
221
  }
217
222
 
@@ -375,6 +380,7 @@ if(-not($env:ImageOS) -and -not($env:ImageVersion)) {
375
380
  if(-not(Test-Path -LiteralPath $current_profile)) {
376
381
  New-Item -Path $current_profile -ItemType "file" -Force >$null 2>&1
377
382
  }
383
+ Add-Path -PathItem $bin_dir -Force
378
384
  }
379
385
 
380
386
  $src = Join-Path -Path $PSScriptRoot -ChildPath \..