gowalk-cicd 1.0.4 → 1.0.5

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.
package/README.md CHANGED
@@ -147,7 +147,7 @@ configuration for either:
147
147
  |---|---|---|
148
148
  | Detected by | `pubspec.yaml` at the repo root | `settings.gradle(.kts)` + `gradlew` at the root or under `android/` |
149
149
  | App module | `android/app` | the one module applying `com.android.application` (version-catalog aliases are resolved; a module named `app` wins a tie against a wear/automotive sibling) |
150
- | Tests (`run-tests`) | `flutter analyze` + `flutter test` | `<module>:testReleaseUnitTest` |
150
+ | Tests (`run-tests`) | `flutter analyze` + `flutter test` | `gradlew test` (all variants, all modules) |
151
151
  | Build | `flutter build appbundle --release` | `<module>:bundleRelease` |
152
152
  | Toolchain installed | Flutter + JDK 17 | JDK 21 only |
153
153
 
@@ -1 +1 @@
1
- 1.0.4
1
+ 1.0.5
@@ -52,8 +52,15 @@ from cfg_io import log, notice
52
52
  APP_PRODUCT_TYPE = "com.apple.product-type.application"
53
53
  # Timeouts (seconds) — xcodebuild can hang on missing toolchains or
54
54
  # package resolution. CI should fail fast rather than spin forever.
55
- LIST_TIMEOUT = 60
56
- SETTINGS_TIMEOUT = 120
55
+ #
56
+ # The first xcodebuild invocation on a cold runner also resolves the Swift
57
+ # Package Manager graph, and a project that depends on firebase-ios-sdk spends
58
+ # minutes cloning before it prints a single scheme. That is legitimate work,
59
+ # not a hang, so `-list` gets a budget that survives it and a one-shot
60
+ # pre-resolution retry (see _list_schemes) rather than a tighter deadline.
61
+ LIST_TIMEOUT = 180
62
+ SETTINGS_TIMEOUT = 300
63
+ RESOLVE_TIMEOUT = 900
57
64
 
58
65
  # Process-local caches keyed by (project, workspace_file) or
59
66
  # (project, workspace_file, scheme, configuration). Cleared between test
@@ -221,6 +228,39 @@ def _pick_by_basename(matches: list[Path], workspace: Path, label: str) -> Path:
221
228
  return matches[0]
222
229
 
223
230
 
231
+ def _resolve_package_dependencies(
232
+ workspace: Path, project: str, workspace_file: str
233
+ ) -> bool:
234
+ """Resolve the SPM graph once so later xcodebuild calls are cheap.
235
+
236
+ Returns True when resolution finished (regardless of exit status — a
237
+ project with no packages still exits cleanly and a partial resolve may
238
+ be enough for ``-list``), False when it could not be run at all.
239
+ """
240
+ cmd = ["xcodebuild", "-resolvePackageDependencies"]
241
+ if workspace_file:
242
+ cmd += ["-workspace", workspace_file]
243
+ elif project:
244
+ cmd += ["-project", project]
245
+ else:
246
+ return False
247
+ log("auto-detect: pre-resolving Swift package dependencies")
248
+ try:
249
+ result = subprocess.run(
250
+ cmd, cwd=str(workspace), capture_output=True, text=True,
251
+ timeout=RESOLVE_TIMEOUT,
252
+ )
253
+ except (OSError, subprocess.TimeoutExpired) as exc:
254
+ log(f"auto-detect: xcodebuild -resolvePackageDependencies failed: {exc!r}")
255
+ return False
256
+ if result.returncode != 0:
257
+ log(
258
+ f"auto-detect: xcodebuild -resolvePackageDependencies returned "
259
+ f"{result.returncode}: {result.stderr.strip()[:500]}"
260
+ )
261
+ return True
262
+
263
+
224
264
  def _list_schemes(
225
265
  workspace: Path, project: str, workspace_file: str
226
266
  ) -> Optional[list[str]]:
@@ -243,7 +283,27 @@ def _list_schemes(
243
283
  cmd, cwd=str(workspace), capture_output=True, text=True,
244
284
  timeout=LIST_TIMEOUT,
245
285
  )
246
- except (OSError, subprocess.TimeoutExpired) as exc:
286
+ except subprocess.TimeoutExpired as exc:
287
+ # Almost always SPM resolution rather than a hang: `-list` implicitly
288
+ # resolves the package graph, and a cold Firebase/GoogleSignIn clone
289
+ # outlasts any deadline short enough to still catch a real hang. Pay
290
+ # for the resolve once, explicitly, then retry. Swallowing this is what
291
+ # produced the downstream "MARKETING_VERSION for scheme=" failure --
292
+ # an empty scheme reads as "project has none", not "we never asked".
293
+ log(f"auto-detect: xcodebuild -list timed out: {exc!r}")
294
+ if not _resolve_package_dependencies(workspace, project, workspace_file):
295
+ _list_cache[key] = None
296
+ return None
297
+ try:
298
+ result = subprocess.run(
299
+ cmd, cwd=str(workspace), capture_output=True, text=True,
300
+ timeout=LIST_TIMEOUT,
301
+ )
302
+ except (OSError, subprocess.TimeoutExpired) as retry_exc:
303
+ log(f"auto-detect: xcodebuild -list failed after resolve: {retry_exc!r}")
304
+ _list_cache[key] = None
305
+ return None
306
+ except OSError as exc:
247
307
  log(f"auto-detect: xcodebuild -list failed: {exc!r}")
248
308
  _list_cache[key] = None
249
309
  return None
@@ -9,6 +9,7 @@ All tests run offline. xcodebuild invocations are stubbed through
9
9
  from __future__ import annotations
10
10
 
11
11
  import json
12
+ import subprocess
12
13
  import sys
13
14
  import tempfile
14
15
  import unittest
@@ -318,6 +319,54 @@ class AutoDetectSchemeTests(unittest.TestCase):
318
319
  scheme = auto_detect.auto_detect_scheme(root, "Proj.xcodeproj", "")
319
320
  self.assertIsNone(scheme)
320
321
 
322
+ def test_resolves_packages_and_retries_when_list_times_out(self):
323
+ # `-list` implicitly resolves the SPM graph, so a cold runner cloning
324
+ # firebase-ios-sdk blows the deadline on the first call. Treating that
325
+ # as "no schemes" is what left SCHEME empty and failed the build much
326
+ # later, on an unrelated-looking MARKETING_VERSION error.
327
+ calls: list[str] = []
328
+
329
+ def fake_run(cmd, **kw):
330
+ if "-resolvePackageDependencies" in cmd:
331
+ calls.append("resolve")
332
+ result = mock.MagicMock()
333
+ result.returncode = 0
334
+ result.stdout = ""
335
+ return result
336
+ if "-list" in cmd:
337
+ calls.append("list")
338
+ if calls.count("list") == 1:
339
+ raise subprocess.TimeoutExpired(cmd, auto_detect.LIST_TIMEOUT)
340
+ result = mock.MagicMock()
341
+ result.returncode = 0
342
+ result.stdout = self._list_output(["MyApp"])
343
+ return result
344
+ result = mock.MagicMock()
345
+ result.returncode = 0
346
+ result.stdout = self._show_settings(APP_TYPE)
347
+ return result
348
+
349
+ root = Path("/tmp/repo")
350
+ with mock.patch.object(auto_detect.subprocess, "run", side_effect=fake_run):
351
+ scheme = auto_detect.auto_detect_scheme(root, "Proj.xcodeproj", "")
352
+
353
+ self.assertEqual(scheme, "MyApp")
354
+ self.assertEqual(calls[:3], ["list", "resolve", "list"])
355
+
356
+ def test_gives_up_when_list_times_out_again_after_resolving(self):
357
+ def fake_run(cmd, **kw):
358
+ if "-resolvePackageDependencies" in cmd:
359
+ result = mock.MagicMock()
360
+ result.returncode = 0
361
+ result.stdout = ""
362
+ return result
363
+ raise subprocess.TimeoutExpired(cmd, auto_detect.LIST_TIMEOUT)
364
+
365
+ root = Path("/tmp/repo")
366
+ with mock.patch.object(auto_detect.subprocess, "run", side_effect=fake_run):
367
+ scheme = auto_detect.auto_detect_scheme(root, "Proj.xcodeproj", "")
368
+ self.assertIsNone(scheme)
369
+
321
370
 
322
371
  class AutoDetectBundleIdTests(unittest.TestCase):
323
372
  """auto_detect_bundle_id: extract PRODUCT_BUNDLE_IDENTIFIER from showBuildSettings."""
@@ -1 +1 @@
1
- 1.0.4
1
+ 1.0.5
@@ -109,13 +109,19 @@ runs:
109
109
  # resolve_android.py has already rewritten versionCode/versionName in the
110
110
  # module build file, so these are plain Gradle invocations with no extra
111
111
  # properties for the project to have to opt into reading.
112
+ # `test` is the aggregate task ("Run unit tests for all variants") and is the
113
+ # only one guaranteed to exist: AGP only creates testXUnitTest tasks for the
114
+ # variants it enables, and AGP 9 disables the release unit test variant by
115
+ # default, so `:app:testReleaseUnitTest` is simply absent in many projects.
116
+ # Running it unqualified also mirrors `flutter test`, which covers the whole
117
+ # package rather than one module.
112
118
  - name: Test Android app
113
119
  if: ${{ steps.config.outputs.project_kind == 'gradle' && inputs.run-tests == 'true' }}
114
120
  shell: bash
115
121
  working-directory: ${{ steps.config.outputs.gradle_root || '.' }}
116
122
  run: |
117
123
  chmod +x ./gradlew
118
- ./gradlew "${ANDROID_GRADLE_MODULE}:testReleaseUnitTest" --console=plain --stacktrace
124
+ ./gradlew test --console=plain --stacktrace
119
125
 
120
126
  - name: Build release Android App Bundle with Gradle
121
127
  if: ${{ steps.config.outputs.project_kind == 'gradle' }}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gowalk-cicd",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "description": "Zero-config GitHub Actions delivery for iOS TestFlight and Android Google Play.",
5
5
  "type": "module",
6
6
  "bin": {