setup-php 2.35.5 → 2.37.0

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 (58) hide show
  1. package/README.md +103 -90
  2. package/lib/core.d.ts +8 -0
  3. package/lib/core.js +55 -0
  4. package/lib/core.js.map +1 -0
  5. package/lib/extensions.js +17 -16
  6. package/lib/extensions.js.map +1 -1
  7. package/lib/fetch.js +25 -70
  8. package/lib/fetch.js.map +1 -1
  9. package/lib/install.js +1 -1
  10. package/lib/install.js.map +1 -1
  11. package/lib/tools.d.ts +48 -21
  12. package/lib/tools.js +175 -154
  13. package/lib/tools.js.map +1 -1
  14. package/lib/utils.js +5 -5
  15. package/lib/utils.js.map +1 -1
  16. package/package.json +23 -22
  17. package/src/configs/brew_extensions +20 -0
  18. package/src/configs/darwin_libs +22 -0
  19. package/src/configs/linux_libs +22 -0
  20. package/src/configs/php-versions.json +5 -4
  21. package/src/configs/tools.json +2 -1
  22. package/src/core.ts +112 -0
  23. package/src/extensions.ts +39 -32
  24. package/src/fetch.ts +28 -42
  25. package/src/install.ts +1 -1
  26. package/src/scripts/darwin.sh +45 -19
  27. package/src/scripts/extensions/add_extensions.sh +5 -3
  28. package/src/scripts/extensions/couchbase.sh +13 -2
  29. package/src/scripts/extensions/firebird.sh +6 -23
  30. package/src/scripts/extensions/gearman.sh +3 -1
  31. package/src/scripts/extensions/http.ps1 +7 -5
  32. package/src/scripts/extensions/ibm.ps1 +56 -0
  33. package/src/scripts/extensions/ibm.sh +106 -0
  34. package/src/scripts/extensions/oci.sh +2 -1
  35. package/src/scripts/extensions/patches/amqp.sh +5 -0
  36. package/src/scripts/extensions/patches/common.sh +31 -8
  37. package/src/scripts/extensions/patches/geos.sh +1 -5
  38. package/src/scripts/extensions/patches/pdo_oci.sh +3 -0
  39. package/src/scripts/extensions/patches/pdo_sqlsrv.sh +5 -0
  40. package/src/scripts/extensions/patches/phpize.sh +2 -1
  41. package/src/scripts/extensions/relay.sh +25 -35
  42. package/src/scripts/extensions/source.sh +26 -6
  43. package/src/scripts/extensions/sqlsrv.ps1 +2 -0
  44. package/src/scripts/extensions/sqlsrv.sh +2 -0
  45. package/src/scripts/linux.sh +50 -10
  46. package/src/scripts/tools/add_tools.ps1 +52 -26
  47. package/src/scripts/tools/add_tools.sh +41 -19
  48. package/src/scripts/tools/blackfire.sh +1 -1
  49. package/src/scripts/tools/brew.sh +130 -0
  50. package/src/scripts/tools/grpc_php_plugin.sh +2 -2
  51. package/src/scripts/tools/ppa.sh +201 -28
  52. package/src/scripts/tools/symfony.ps1 +28 -14
  53. package/src/scripts/tools/symfony.sh +34 -31
  54. package/src/scripts/unix.sh +8 -4
  55. package/src/scripts/win32.ps1 +9 -4
  56. package/src/tools.ts +312 -203
  57. package/src/utils.ts +6 -7
  58. package/src/scripts/extensions/patches/gearman.sh +0 -5
@@ -1,3 +1,7 @@
1
+ os="$(uname -s)"
2
+ os_lower=$(echo "$os" | tr '[:upper:]' '[:lower:]')
3
+ os_capital=$(echo "$os" | tr '[:lower:]' '[:upper:]')
4
+
1
5
  # Function to parse extension environment variables
2
6
  parse_args() {
3
7
  local extension=${1%-*}
@@ -36,7 +40,7 @@ add_lib_log() {
36
40
  # Function to check if a library is installed
37
41
  check_lib() {
38
42
  local lib=$1
39
- if [ "$(uname -s)" = "Linux" ]; then
43
+ if [ "$os" = "Linux" ]; then
40
44
  [ "x$(dpkg -s "$lib" 2>/dev/null | grep Status)" != "x" ]
41
45
  else
42
46
  [ "x$(find "${brew_prefix:?}"/Cellar -maxdepth 1 -name "$lib")" != "x" ]
@@ -56,9 +60,11 @@ add_linux_libs() {
56
60
  add_darwin_libs() {
57
61
  local lib=$1
58
62
  if ! check_lib "$lib"; then
59
- brew install "$lib" >/dev/null 2>&1 || true
60
63
  if [[ "$lib" = *@* ]]; then
64
+ safe_brew install --skip-link "$lib" >/dev/null 2>&1 || true
61
65
  brew link --overwrite --force "$lib" >/dev/null 2>&1 || true
66
+ else
67
+ safe_brew install "$lib" >/dev/null 2>&1 || true
62
68
  fi
63
69
  fi
64
70
  add_lib_log "$lib"
@@ -68,7 +74,7 @@ add_darwin_libs() {
68
74
  add_libs() {
69
75
  local all_libs=("$@")
70
76
  for lib in "${all_libs[@]}"; do
71
- if [ "$(uname -s)" = "Linux" ]; then
77
+ if [ "$os" = "Linux" ]; then
72
78
  add_linux_libs "$lib"
73
79
  else
74
80
  add_darwin_libs "$lib"
@@ -76,6 +82,19 @@ add_libs() {
76
82
  done
77
83
  }
78
84
 
85
+ # Function to get required libraries for an extension
86
+ get_libraries() {
87
+ local extension=$1
88
+ {
89
+ parse_args "$extension" LIBS
90
+ parse_args "$extension" "$os_capital"_LIBS
91
+ [ -r "${src:?}/configs/${os_lower}_libs" ] && \
92
+ grep -E "^[[:space:]]*${extension}[[:space:]]*=" "${src:?}/configs/${os_lower}_libs" | \
93
+ head -n1 | \
94
+ sed -E "s/^[[:space:]]*${extension}[[:space:]]*=[[:space:]]*//"
95
+ } | xargs -n 1 2>/dev/null | sort -u | xargs 2>/dev/null
96
+ }
97
+
79
98
  # Function to run command in a group
80
99
  run_group() {
81
100
  local command=$1
@@ -119,9 +138,10 @@ fetch_extension() {
119
138
  elif [ "$fetch" = "pecl" ]; then
120
139
  source="pecl"
121
140
  pecl_name=${extension/http/pecl_http}
122
- get -q -n /tmp/"$pecl_name".tgz https://pecl.php.net/get/"$pecl_name"-"$release".tgz
141
+ capital_pecl_name=$(echo "$pecl_name" | tr '[:lower:]' '[:upper:]')
142
+ get -q -n /tmp/"$pecl_name".tgz https://pecl.php.net/get/"$pecl_name"-"$release".tgz https://pecl.php.net/get/"$capital_pecl_name"-"$release".tgz
123
143
  tar -xzf /tmp/"$pecl_name".tgz -C /tmp
124
- cd /tmp/"$pecl_name"-"$release" || exit
144
+ cd /tmp/"$pecl_name"-"$release" 2>/dev/null || cd /tmp/"$capital_pecl_name"-"$release" 2>/dev/null || exit
125
145
  fi
126
146
  }
127
147
 
@@ -136,7 +156,7 @@ add_extension_from_source() {
136
156
  local fetch=${7:-clone}
137
157
  slug="$extension-$release"
138
158
  source="$url/$org/$repo"
139
- libraries="$(parse_args "$extension" LIBS) $(parse_args "$extension" "$(uname -s)"_LIBS)"
159
+ libraries="$(get_libraries "$extension")"
140
160
  opts="$(parse_args "$extension" CONFIGURE_OPTS)"
141
161
  prefix_opts="$(parse_args "$extension" CONFIGURE_PREFIX_OPTS)"
142
162
  suffix_opts="$(parse_args "$extension" CONFIGURE_SUFFIX_OPTS)"
@@ -9,6 +9,8 @@ Function Get-SqlsrvReleaseVersion() {
9
9
  return '5.10.1'
10
10
  } elseif ($version -eq '8.0') {
11
11
  return '5.11.1'
12
+ } elseif ($version -match '8.[1-2]') {
13
+ return '5.12.0'
12
14
  } else {
13
15
  return 'latest'
14
16
  }
@@ -6,6 +6,8 @@ get_sqlsrv_version() {
6
6
  echo '5.10.1'
7
7
  elif [[ "${version:?}" =~ 8.0 ]]; then
8
8
  echo '5.11.1'
9
+ elif [[ "${version:?}" =~ 8.[1-2] ]]; then
10
+ echo '5.12.0'
9
11
  else
10
12
  # Return an empty string so that pecl will install the latest version.
11
13
  echo ''
@@ -127,6 +127,51 @@ setup_cached_versions() {
127
127
  run_script "php-ubuntu" "$version" "${debug:?}" "${ts:?}"
128
128
  }
129
129
 
130
+ # Function to get the link path for an alternative.
131
+ alternative_link() {
132
+ case "$1" in
133
+ php-cgi-bin) echo "/usr/lib/cgi-bin/php" ;;
134
+ php-fpm) echo "/usr/sbin/php-fpm" ;;
135
+ php-fpm.sock) echo "/run/php/php-fpm.sock" ;;
136
+ *) echo "/usr/bin/$1" ;;
137
+ esac
138
+ }
139
+
140
+ # Function to get the target path for an alternative.
141
+ alternative_target() {
142
+ case "$1" in
143
+ php-cgi-bin) echo "/usr/lib/cgi-bin/php$version" ;;
144
+ php-fpm) echo "/usr/sbin/php-fpm$version" ;;
145
+ php-fpm.sock) echo "/run/php/php$version-fpm.sock" ;;
146
+ *) echo "/usr/bin/$1$version" ;;
147
+ esac
148
+ }
149
+
150
+ # Function to register an alternative if the versioned binary exists but is missing.
151
+ register_alternative() {
152
+ local tool=$1
153
+ local link target priority state_file
154
+ target="$(alternative_target "$tool")"
155
+ [ -e "$target" ] || return 0
156
+ state_file="/var/lib/dpkg/alternatives/$tool"
157
+ if sudo test -r "$state_file" && sudo grep -Fxq "$target" "$state_file"; then
158
+ return 0
159
+ fi
160
+ link="$(alternative_link "$tool")"
161
+ priority="${version//./}"
162
+ sudo update-alternatives --install "$link" "$tool" "$target" "$priority" >/dev/null 2>&1
163
+ }
164
+
165
+ # Function to register and switch an alternative.
166
+ set_alternative() {
167
+ local tool=$1
168
+ local target
169
+ target="$(alternative_target "$tool")"
170
+ [ -e "$target" ] || return 0
171
+ register_alternative "$tool" || return 1
172
+ sudo update-alternatives --set "$tool" "$target" >/dev/null 2>&1
173
+ }
174
+
130
175
  # Function to add PECL.
131
176
  add_pecl() {
132
177
  add_devtools phpize >/dev/null 2>&1
@@ -143,16 +188,11 @@ switch_version() {
143
188
  tools=("$@")
144
189
  to_wait=()
145
190
  if ! (( ${#tools[@]} )); then
146
- tools+=(pear pecl php phar phar.phar php-cgi php-config phpize phpdbg)
147
- [ -e /usr/lib/cgi-bin/php"$version" ] && sudo update-alternatives --set php-cgi-bin /usr/lib/cgi-bin/php"$version" & to_wait+=($!)
148
- [ -e /usr/sbin/php-fpm"$version" ] && sudo update-alternatives --set php-fpm /usr/sbin/php-fpm"$version" & to_wait+=($!)
149
- [ -e /run/php/php"$version"-fpm.sock ] && sudo update-alternatives --set php-fpm.sock /run/php/php"$version"-fpm.sock & to_wait+=($!)
191
+ tools+=(php-cgi-bin php-fpm php-fpm.sock pear pecl php phar phar.phar php-cgi php-config phpize phpdbg)
150
192
  fi
151
193
  for tool in "${tools[@]}"; do
152
- if [ -e "/usr/bin/$tool$version" ]; then
153
- sudo update-alternatives --set "$tool" /usr/bin/"$tool$version" &
154
- to_wait+=($!)
155
- fi
194
+ set_alternative "$tool" &
195
+ to_wait+=($!)
156
196
  done
157
197
  wait "${to_wait[@]}"
158
198
  }
@@ -187,7 +227,7 @@ update_php() {
187
227
  # Function to install PHP.
188
228
  add_php() {
189
229
  if [ "${runner:?}" = "self-hosted" ] || [ "${use_package_cache:-true}" = "false" ]; then
190
- if [[ "$version" =~ ${nightly_versions:?} || "$ts" = "zts" ]]; then
230
+ if [[ "$version" =~ ${php_builder_versions:?} || "$ts" = "zts" ]]; then
191
231
  setup_php_builder
192
232
  else
193
233
  add_packaged_php
@@ -291,7 +331,7 @@ setup_php() {
291
331
  }
292
332
 
293
333
  # Variables
294
- version=${1:-'8.4'}
334
+ version=${1:-'8.5'}
295
335
  ini=${2:-'production'}
296
336
  src=${0%/*}/..
297
337
  debconf_fix="DEBIAN_FRONTEND=noninteractive"
@@ -115,6 +115,18 @@ Function Set-ComposerEnv() {
115
115
  }
116
116
  }
117
117
 
118
+ # Function to identify latest-like URLs that should bypass the persistent cache.
119
+ Function Test-MutableToolUrl() {
120
+ Param(
121
+ [Parameter(Position = 0, Mandatory = $true)]
122
+ [string]
123
+ $Url
124
+ )
125
+ $mutableUrlRegex = '(^|[/?#._=-])(latest|stable|preview|snapshot|nightly|master)([/?#._=-]|$)|/releases/latest/download/'
126
+ $versionLikeRegex = '(^|[^0-9])[0-9]+\.[0-9]+([.-][0-9A-Za-z]+)*'
127
+ return ($Url -match $mutableUrlRegex) -or (($Url -match '\.phar([?#].*)?$') -and -not ($Url -match $versionLikeRegex))
128
+ }
129
+
118
130
  # Function to extract tool version.
119
131
  Function Get-ToolVersion() {
120
132
  Param (
@@ -158,12 +170,7 @@ Function Add-ToolsHelper() {
158
170
  } elseif($tool -eq "cs2pr") {
159
171
  (Get-Content $bin_dir/cs2pr).replace('exit(9)', 'exit(0)') | Set-Content $bin_dir/cs2pr
160
172
  } 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
- }
173
+ Copy-Item $bin_dir\deployer.bat -Destination $bin_dir\dep.bat
167
174
  } elseif($tool -eq "phan") {
168
175
  $extensions += @('fileinfo', 'ast')
169
176
  } elseif($tool -eq "phinx") {
@@ -205,30 +212,51 @@ Function Add-Tool() {
205
212
  [Parameter(Position = 2, Mandatory = $false)]
206
213
  $ver_param
207
214
  )
208
- if (Test-Path $bin_dir\$tool) {
209
- Copy-Item $bin_dir\$tool -Destination $bin_dir\$tool.old -Force
210
- }
215
+ $urls = $urls -split ','
211
216
  $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 { }
217
+ $is_exe = ((($urls[0] | Split-Path -Extension).ToLowerInvariant()) -eq '.exe')
218
+ if ($is_exe) { $tool_path = "$tool_path.exe" }
219
+ $tool_ext = if ($is_exe) { '.exe' } else { '' }
220
+ $url_stream = [System.IO.MemoryStream]::New([System.Text.Encoding]::UTF8.GetBytes($urls[0]))
221
+ $cache_key = (Get-FileHash -InputStream $url_stream -Algorithm SHA256).Hash.Substring(0, 16)
222
+ $cache_path = "$env:TEMP\$tool-$cache_key$tool_ext"
223
+ $use_cache = -not (Test-MutableToolUrl $urls[0])
224
+ $status_code = 200
225
+ if ($use_cache -and (Test-Path $cache_path -PathType Leaf)) {
226
+ Copy-Item $cache_path -Destination $tool_path -Force
227
+ } else {
228
+ $backup_path = "$tool_path.bak"
229
+ if (Test-Path $tool_path) { Copy-Item $tool_path -Destination $backup_path -Force }
230
+ foreach ($url in $urls){
231
+ try {
232
+ $status_code = (Invoke-WebRequest -Passthru -Uri $url -OutFile $tool_path).StatusCode
233
+ } catch {
234
+ if($url -match '.*github.com.*releases.*latest.*') {
235
+ try {
236
+ $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])
237
+ $status_code = (Invoke-WebRequest -Passthru -Uri $url -OutFile $tool_path).StatusCode
238
+ } catch {
239
+ $status_code = 0
240
+ }
241
+ } else {
242
+ $status_code = 0
243
+ }
244
+ }
245
+ if($status_code -eq 200 -and (Test-Path $tool_path)) {
246
+ if ($use_cache) {
247
+ Copy-Item $tool_path -Destination $cache_path -Force
248
+ }
249
+ break
224
250
  }
225
251
  }
226
- if($status_code -eq 200 -and (Test-Path $tool_path)) {
227
- break
252
+ if ($status_code -ne 200 -and (Test-Path $backup_path)) {
253
+ Copy-Item $backup_path -Destination $tool_path -Force
228
254
  }
255
+ Remove-Item $backup_path -Force -ErrorAction SilentlyContinue
229
256
  }
230
257
 
231
- if (((Get-ChildItem -Path $bin_dir/* | Where-Object Name -Match "^$tool(.exe|.phar)*$").Count -gt 0)) {
258
+ $escaped_tool = [regex]::Escape($tool)
259
+ if (((Get-ChildItem -Path $bin_dir/* | Where-Object Name -Match "^$escaped_tool(\.exe|\.phar)?$").Count -gt 0)) {
232
260
  $bat_content = @()
233
261
  $bat_content += "@ECHO off"
234
262
  $bat_content += "setlocal DISABLEDELAYEDEXPANSION"
@@ -242,8 +270,6 @@ Function Add-Tool() {
242
270
  } else {
243
271
  if($tool -eq "composer") {
244
272
  $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
273
  }
248
274
  Add-Log $cross $tool "Could not add $tool"
249
275
  }
@@ -113,6 +113,16 @@ set_composer_env() {
113
113
  fi
114
114
  }
115
115
 
116
+ # Function to identify latest-like URLs that should bypass the persistent cache.
117
+ is_mutable_tool_url() {
118
+ local tool_url=$1
119
+ local mutable_url_regex='(^|[/?#._=-])(latest|stable|preview|snapshot|nightly|master)([/?#._=-]|$)|/releases/latest/download/'
120
+ local version_like_regex='(^|[^0-9])[0-9]+\.[0-9]+([.-][0-9A-Za-z]+)*'
121
+ [[ "$tool_url" =~ $mutable_url_regex ]] && return 0
122
+ [[ "$tool_url" =~ \.phar([?#].*)?$ && ! "$tool_url" =~ $version_like_regex ]] && return 0
123
+ return 1
124
+ }
125
+
116
126
  # Helper function to configure tools.
117
127
  add_tools_helper() {
118
128
  tool=$1
@@ -123,19 +133,15 @@ add_tools_helper() {
123
133
  extensions+=(iconv mbstring phar sodium)
124
134
  elif [ "$tool" = "codeception" ]; then
125
135
  extensions+=(json mbstring)
126
- sudo ln -s "$scoped_dir"/vendor/bin/codecept "$scoped_dir"/vendor/bin/codeception
136
+ sudo ln -s "$scoped_dir"/vendor/bin/codecept "$scoped_dir"/vendor/bin/codeception 2>/dev/null || true
127
137
  elif [ "$tool" = "composer" ]; then
128
138
  configure_composer "$tool_path"
129
139
  elif [ "$tool" = "cs2pr" ]; then
130
140
  sudo sed -i 's/\r$//; s/exit(9)/exit(0)/' "$tool_path" 2>/dev/null ||
131
141
  sudo sed -i '' 's/\r$//; s/exit(9)/exit(0)/' "$tool_path"
132
142
  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
143
+ sudo ln -s "$tool_path" "$tool_path_dir"/deployer 2>/dev/null || true
144
+ sudo ln -s "$tool_path" "$tool_path_dir"/dep 2>/dev/null || true
139
145
  elif [ "$tool" = "phan" ]; then
140
146
  extensions+=(fileinfo ast)
141
147
  elif [ "$tool" = "phinx" ]; then
@@ -151,7 +157,7 @@ add_tools_helper() {
151
157
  elif [ "$tool" = "phpDocumentor" ]; then
152
158
  extensions+=(ctype hash json fileinfo iconv mbstring simplexml xml)
153
159
  sudo ln -s "$tool_path" "$tool_path_dir"/phpdocumentor 2>/dev/null || true
154
- sudo ln -s "$tool_path" "$tool_path_dir"/phpdoc
160
+ sudo ln -s "$tool_path" "$tool_path_dir"/phpdoc 2>/dev/null || true
155
161
  elif [ "$tool" = "phpunit" ]; then
156
162
  extensions+=(dom json libxml mbstring xml xmlwriter)
157
163
  elif [ "$tool" = "phpunit-bridge" ]; then
@@ -162,9 +168,9 @@ add_tools_helper() {
162
168
  fi
163
169
  elif [ "$tool" = "vapor-cli" ]; then
164
170
  extensions+=(fileinfo json mbstring zip simplexml)
165
- sudo ln -s "$scoped_dir"/vendor/bin/vapor "$scoped_dir"/vendor/bin/vapor-cli
171
+ sudo ln -s "$scoped_dir"/vendor/bin/vapor "$scoped_dir"/vendor/bin/vapor-cli 2>/dev/null || true
166
172
  elif [ "$tool" = wp-cli ]; then
167
- sudo ln -s "$tool_path" "$tool_path_dir"/"${tool%-*}"
173
+ sudo ln -s "$tool_path" "$tool_path_dir"/"${tool%-*}" 2>/dev/null || true
168
174
  fi
169
175
  for extension in "${extensions[@]}"; do
170
176
  add_extension "$extension" extension >/dev/null 2>&1
@@ -180,25 +186,41 @@ add_tool() {
180
186
  if ! [ -d "$tool_path_dir" ]; then
181
187
  sudo mkdir -p "$tool_path_dir"
182
188
  fi
183
- add_path "$tool_path_dir"
184
- if [ -e "$tool_path" ]; then
185
- sudo cp -aL "$tool_path" /tmp/"$tool"
189
+ if ! [ -d "$tool_cache_path_dir" ]; then
190
+ sudo mkdir -p "$tool_cache_path_dir"
186
191
  fi
192
+ add_path "$tool_path_dir" verify
193
+ add_path "$tool_cache_path_dir"
187
194
  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]}")
195
+ cache_key=$(get_sha256 "${url[0]}" | head -c 16)
196
+ cache_path="$tool_cache_path_dir/${tool}-${cache_key}"
197
+ use_cache=true
198
+ is_mutable_tool_url "${url[0]}" && use_cache=false
199
+ status_code="200"
200
+ if [ "$use_cache" = "true" ] && [ -f "$cache_path" ]; then
201
+ sudo cp -a "$cache_path" "$tool_path"
202
+ else
203
+ [ -f "$tool_path" ] && sudo cp -a "$tool_path" "$tool_path.bak"
204
+ status_code=$(get -v -e "$tool_path" "${url[@]}")
205
+ if [ "$status_code" != "200" ] && [[ "${url[0]}" =~ .*github.com.*releases.*latest.* ]]; then
206
+ 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)}"
207
+ status_code=$(get -v -e "$tool_path" "${url[0]}")
208
+ fi
209
+ if [ "$status_code" = "200" ] && [ "$use_cache" = "true" ]; then
210
+ sudo cp -a "$tool_path" "$cache_path"
211
+ elif [ -f "$tool_path.bak" ]; then
212
+ sudo mv "$tool_path.bak" "$tool_path"
213
+ fi
214
+ sudo rm -f "$tool_path.bak"
192
215
  fi
193
216
  if [ "$status_code" = "200" ]; then
194
217
  add_tools_helper "$tool"
218
+ [ -L "$tool_cache_path_dir/$tool" ] || sudo ln -s "$tool_path" "$tool_cache_path_dir/$tool" 2>/dev/null || true
195
219
  tool_version=$(get_tool_version "$tool" "$ver_param")
196
220
  add_log "${tick:?}" "$tool" "Added $tool $tool_version"
197
221
  else
198
222
  if [ "$tool" = "composer" ]; then
199
223
  export fail_fast=true
200
- elif [ -e /tmp/"$tool" ]; then
201
- sudo cp -a /tmp/"$tool" "$tool_path"
202
224
  fi
203
225
  if [ "$status_code" = "404" ]; then
204
226
  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]+")"