From 78bc8822b0db23551d6b51f484e7f063659ac008 Mon Sep 17 00:00:00 2001 From: Gavyn Caldwell Date: Sun, 1 Nov 2026 15:36:01 -0700 Subject: [PATCH 1/2] fix: build against SDK main after the LightJobResult signature change The upstream audio rewrite gave LightJobResult.Success two required fields and the summary no longer travelled via output data alone. The call site carries both fields explicitly. --- .../main/kotlin/com/loosewire/borealis/BorealisSyncJob.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/src/main/kotlin/com/loosewire/borealis/BorealisSyncJob.kt b/app/src/main/kotlin/com/loosewire/borealis/BorealisSyncJob.kt index 04acd9d..acccb89 100644 --- a/app/src/main/kotlin/com/loosewire/borealis/BorealisSyncJob.kt +++ b/app/src/main/kotlin/com/loosewire/borealis/BorealisSyncJob.kt @@ -11,7 +11,11 @@ val borealisSyncJob: LightJobHandler = { context, _ -> val result = BorealisServices.from(context).repository.syncAndUpdate() result.fold( onSuccess = { summary -> - LightJobResult.Success(mapOf("message" to summary.message)) + LightJobResult.Success( + mapOf("message" to summary.message), + outputFilePath = null, + message = null, + ) }, onFailure = { error -> LightJobResult.Retry From c7d32515ebb731cff1064c2c531511baa1b5c2cd Mon Sep 17 00:00:00 2001 From: Gavyn Caldwell Date: Sun, 1 Nov 2026 15:50:49 -0700 Subject: [PATCH 2/2] build: build against the SDK fork instead of a pinned base plus patch The SDK fork now carries the package-install, Play sign-in, uninstall, pinned dependency, and exact-version allowlist work that used to be applied here as a patch plus a checked-in extension. All of it is now the SDK's own code, so the build needs no local patch step and no extension installer, and the release provenance fetches the full SDK tree rather than a base-plus-delta. As a side effect this moves Borealis off 52fbc5a and onto the fork's main. --- .github/actions/setup-android/action.yml | 17 ++-- scripts/apply-sdk-extension.py | 95 ------------------ scripts/release-package.py | 21 +--- scripts/test_apply_sdk_extension.py | 122 ----------------------- 4 files changed, 9 insertions(+), 246 deletions(-) delete mode 100644 scripts/apply-sdk-extension.py delete mode 100644 scripts/test_apply_sdk_extension.py diff --git a/.github/actions/setup-android/action.yml b/.github/actions/setup-android/action.yml index 4578ddf..26217b5 100644 --- a/.github/actions/setup-android/action.yml +++ b/.github/actions/setup-android/action.yml @@ -13,23 +13,18 @@ runs: java-version: '17' - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: + # Borealis depends on the exact SDK revision; the guard below fails the + # build if it ever drifts. This revision carries the package-install + # and Google-Play-sign-in work in-tree, so no local patch is applied. repository: gavdevs/light-sdk - ref: 52fbc5a8aedbd3c4c88037580709e53540086229 + ref: b1a2d859df773c2d145fdc8fb65d11e108671d5c path: .ci/light-sdk persist-credentials: false - - name: Apply the checked-in Borealis SDK patch + - name: Assert the pinned SDK revision shell: bash run: | set -euo pipefail - test "$(git -C .ci/light-sdk rev-parse HEAD)" = '52fbc5a8aedbd3c4c88037580709e53540086229' - git -C .ci/light-sdk apply --check "$GITHUB_WORKSPACE/patches/light-sdk-borealis.patch" - git -C .ci/light-sdk apply "$GITHUB_WORKSPACE/patches/light-sdk-borealis.patch" - - name: Apply the checked-in Borealis SDK extension - shell: bash - run: | - set -euo pipefail - python3 -B -m unittest discover -s scripts -p test_apply_sdk_extension.py - python3 -B scripts/apply-sdk-extension.py .ci/light-sdk + test "$(git -C .ci/light-sdk rev-parse HEAD)" = 'b1a2d859df773c2d145fdc8fb65d11e108671d5c' - uses: gradle/actions/setup-gradle@748248ddd2a24f49513d8f472f81c3a07d4d50e1 # v4.4.4 with: cache-read-only: ${{ inputs.cache-read-only == 'true' || github.event_name == 'pull_request' }} diff --git a/scripts/apply-sdk-extension.py b/scripts/apply-sdk-extension.py deleted file mode 100644 index 82bce9f..0000000 --- a/scripts/apply-sdk-extension.py +++ /dev/null @@ -1,95 +0,0 @@ -#!/usr/bin/env python3 -"""Add Borealis's isolated Kotlin extension to a fresh, patched Light SDK.""" - -import argparse -from pathlib import Path - - -EXTENSION_ROOT = Path(__file__).resolve().parent.parent / 'sdk-extension' -SOURCE_TREES = ( - Path('client/src/main/kotlin'), - Path('client/src/test/kotlin'), -) -MAX_FILES = 64 -MAX_FILE_BYTES = 512 * 1024 -MAX_TOTAL_BYTES = 4 * 1024 * 1024 - - -def reject_symlinks(path): - for component in (path, *path.parents): - if component.is_symlink(): - raise ValueError(f'Symlinks are not allowed: {component}') - - -def apply_extension(extension, sdk): - extension = Path(extension).absolute() - sdk = Path(sdk).absolute() - reject_symlinks(extension) - reject_symlinks(sdk) - if not extension.is_dir() or not (sdk / 'sdk/client').is_dir(): - raise ValueError('Expected an extension directory and an existing SDK client module.') - extension = extension.resolve() - sdk = sdk.resolve() - if extension.is_relative_to(sdk) or sdk.is_relative_to(extension): - raise ValueError('Extension and SDK directories must not overlap.') - - plans = [] - total_bytes = 0 - for source in sorted(extension.rglob('*')): - reject_symlinks(source) - relative = source.relative_to(extension) - if source.is_dir(): - if not any(relative.is_relative_to(tree) or tree.is_relative_to(relative) - for tree in SOURCE_TREES): - raise ValueError(f'Unsupported extension directory: {relative}') - continue - if (not source.is_file() or source.suffix != '.kt' or - not any(relative.is_relative_to(tree) for tree in SOURCE_TREES)): - raise ValueError(f'Only Kotlin files in the approved source trees are allowed: {relative}') - if len(plans) >= MAX_FILES: - raise ValueError('Too many extension files.') - with source.open('rb') as stream: - contents = stream.read(MAX_FILE_BYTES + 1) - if len(contents) > MAX_FILE_BYTES: - raise ValueError(f'Extension file is too large: {relative}') - total_bytes += len(contents) - if total_bytes > MAX_TOTAL_BYTES: - raise ValueError('Extension exceeds the total size limit.') - - target = sdk / 'sdk' / relative - reject_symlinks(target) - if not target.resolve().is_relative_to(sdk / 'sdk/client'): - raise ValueError(f'Extension target escapes the SDK client module: {relative}') - if target.exists(): - raise ValueError(f'Refusing to overwrite an existing SDK path: {target}') - for parent in target.parents: - if parent.exists() and not parent.is_dir(): - raise ValueError(f'Extension target parent is not a directory: {parent}') - if parent == sdk: - break - plans.append((target, contents)) - - if not plans: - raise ValueError('No Kotlin extension files found.') - for target, contents in plans: - reject_symlinks(target) - target.parent.mkdir(parents=True, exist_ok=True) - reject_symlinks(target) - with target.open('xb') as stream: - stream.write(contents) - return [target.relative_to(sdk) for target, _ in plans] - - -def main(): - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument('sdk', type=Path, help='Fresh SDK checkout after applying the Borealis patch') - args = parser.parse_args() - try: - copied = apply_extension(EXTENSION_ROOT, args.sdk) - except (OSError, ValueError) as error: - parser.exit(1, f'Cannot apply SDK extension: {error}\n') - print(f'Applied {len(copied)} Kotlin extension files to the SDK client module.') - - -if __name__ == '__main__': - main() diff --git a/scripts/release-package.py b/scripts/release-package.py index ed462d0..a9274fe 100644 --- a/scripts/release-package.py +++ b/scripts/release-package.py @@ -20,7 +20,7 @@ ) ROOT = Path(__file__).resolve().parent.parent -SDK_REVISION = '52fbc5a8aedbd3c4c88037580709e53540086229' +SDK_REVISION = 'b1a2d859df773c2d145fdc8fb65d11e108671d5c' GPLAY_REVISION = '18ec2bd74995d30e500b756359a4de3e37976f03' output = ROOT / 'release-output' output.mkdir(exist_ok=True) @@ -44,13 +44,6 @@ def sha256(path): if git(sdk, 'rev-parse', 'HEAD') != SDK_REVISION: raise SystemExit('Unexpected SDK revision.') -extension_files = sorted((ROOT / 'sdk-extension/client').rglob('*.kt')) -if not extension_files: - raise SystemExit('SDK extension sources are missing.') -for source in extension_files: - built_source = sdk / 'sdk' / source.relative_to(ROOT / 'sdk-extension') - if not built_source.is_file() or sha256(source) != sha256(built_source): - raise SystemExit('Built SDK extension differs from the corresponding source.') commit = git(ROOT, 'rev-parse', 'HEAD') tag = f"v{tool['versionName']}" if os.environ.get('RELEASE_TAG') != tag: @@ -92,9 +85,7 @@ def sha256(path): (output / 'release-build.json').write_text(json.dumps(build, indent=2) + '\n') subprocess.run(['git', 'archive', '--format=tar.gz', '--prefix=borealis/', f'--output={output / "borealis-source.tar.gz"}', commit], cwd=ROOT, check=True) -subprocess.run(['git', 'archive', '--format=tar.gz', '--prefix=light-sdk/', f'--output={output / "light-sdk-base-source.tar.gz"}', SDK_REVISION], cwd=sdk, check=True) -shutil.copyfile(ROOT / 'patches/light-sdk-borealis.patch', output / 'light-sdk-borealis.patch') -shutil.copyfile(ROOT / 'patches/LICENSE.light-sdk', output / 'LICENSE.light-sdk') +subprocess.run(['git', 'archive', '--format=tar.gz', '--prefix=light-sdk/', f'--output={output / "light-sdk-source.tar.gz"}', SDK_REVISION], cwd=sdk, check=True) shutil.copyfile(ROOT / 'licenses/LICENSE.tink', output / 'LICENSE.tink') shutil.copyfile(ROOT / 'LICENSE', output / 'LICENSE') shutil.copyfile(ROOT / 'THIRD_PARTY_NOTICES.md', output / 'THIRD_PARTY_NOTICES.md') @@ -145,13 +136,7 @@ def sha256(path): 'companionUrl': COMPANION_URL, 'apk': apk_name, 'signingCertificateSha256': expected, - 'sdk': {'repository': 'https://github.com/gavdevs/light-sdk', 'commit': SDK_REVISION, - 'patchSha256': sha256(output / 'light-sdk-borealis.patch'), - 'extensionInstallerSha256': sha256(ROOT / 'scripts/apply-sdk-extension.py'), - 'extensionFiles': { - str(path.relative_to(ROOT)): sha256(path) - for path in extension_files - }}, + 'sdk': {'repository': 'https://github.com/gavdevs/light-sdk', 'commit': SDK_REVISION}, 'gplayapi': {'coordinate': 'com.auroraoss:gplayapi:3.6.4', 'commit': GPLAY_REVISION, 'sourceUrl': source_url, 'sourceSha256': sha256(gplay_archive)}, 'tink': {'coordinate': 'com.google.crypto.tink:tink:1.20.0', 'license': 'Apache-2.0', diff --git a/scripts/test_apply_sdk_extension.py b/scripts/test_apply_sdk_extension.py deleted file mode 100644 index 383340e..0000000 --- a/scripts/test_apply_sdk_extension.py +++ /dev/null @@ -1,122 +0,0 @@ -import importlib.util -from pathlib import Path -import tempfile -import unittest -from unittest.mock import patch - - -SPEC = importlib.util.spec_from_file_location('sdk_extension', Path(__file__).with_name('apply-sdk-extension.py')) -extension_module = importlib.util.module_from_spec(SPEC) -SPEC.loader.exec_module(extension_module) - - -class ApplySdkExtensionTest(unittest.TestCase): - def setUp(self): - temporary = tempfile.TemporaryDirectory() - self.addCleanup(temporary.cleanup) - self.root = Path(temporary.name) - self.extension = self.root / 'extension' - self.sdk = self.root / 'sdk' - self.extension.mkdir() - (self.sdk / 'sdk/client').mkdir(parents=True) - - def add_source(self, relative='client/src/main/kotlin/com/example/SignIn.kt', contents=b'package com.example\n'): - source = self.extension / relative - source.parent.mkdir(parents=True, exist_ok=True) - source.write_bytes(contents) - return source - - def apply(self): - return extension_module.apply_extension(self.extension, self.sdk) - - def test_copies_main_and_test_sources_in_order(self): - test_source = self.add_source('client/src/test/kotlin/com/example/SignInTest.kt') - main_source = self.add_source() - copied = self.apply() - expected = [Path('sdk') / source.relative_to(self.extension) for source in (main_source, test_source)] - self.assertEqual(expected, copied) - for source, relative in zip((main_source, test_source), copied): - self.assertEqual(source.read_bytes(), (self.sdk / relative).read_bytes()) - - def test_refuses_existing_target_before_copying_any_files(self): - self.add_source() - source = self.add_source('client/src/test/kotlin/com/example/SignInTest.kt') - target = self.sdk / 'sdk' / source.relative_to(self.extension) - target.parent.mkdir(parents=True) - target.write_text('existing source') - with self.assertRaisesRegex(ValueError, 'overwrite'): - self.apply() - self.assertEqual('existing source', target.read_text()) - self.assertFalse((self.sdk / 'sdk/client/src/main').exists()) - - def test_rejects_unsupported_source_path(self): - self.add_source('plugin/src/main/kotlin/Unsafe.kt') - with self.assertRaisesRegex(ValueError, 'Unsupported extension directory'): - self.apply() - - def test_rejects_non_kotlin_files(self): - self.add_source('client/src/main/kotlin/unexpected.jar') - with self.assertRaisesRegex(ValueError, 'Only Kotlin files'): - self.apply() - - def test_rejects_source_file_symlink(self): - source = self.add_source() - source.with_name('Link.kt').symlink_to(source) - with self.assertRaisesRegex(ValueError, 'Symlinks'): - self.apply() - - def test_rejects_source_directory_symlink(self): - outside = self.root / 'outside' - outside.mkdir() - (self.extension / 'client').symlink_to(outside, target_is_directory=True) - with self.assertRaisesRegex(ValueError, 'Symlinks'): - self.apply() - - def test_rejects_destination_directory_symlink(self): - self.add_source() - outside = self.root / 'outside' - outside.mkdir() - (self.sdk / 'sdk/client/src').symlink_to(outside, target_is_directory=True) - with self.assertRaisesRegex(ValueError, 'Symlinks'): - self.apply() - self.assertEqual([], list(outside.iterdir())) - - def test_rejects_symlink_to_root(self): - self.add_source() - link = self.root / 'linked-extension' - link.symlink_to(self.extension, target_is_directory=True) - with self.assertRaisesRegex(ValueError, 'Symlinks'): - extension_module.apply_extension(link, self.sdk) - - def test_rejects_empty_extension(self): - with self.assertRaisesRegex(ValueError, 'No Kotlin'): - self.apply() - - def test_rejects_oversized_source(self): - self.add_source(contents=b'12345') - with patch.object(extension_module, 'MAX_FILE_BYTES', 4): - with self.assertRaisesRegex(ValueError, 'too large'): - self.apply() - - def test_rejects_too_many_files_before_copying(self): - self.add_source() - self.add_source('client/src/test/kotlin/Test.kt') - with patch.object(extension_module, 'MAX_FILES', 1): - with self.assertRaisesRegex(ValueError, 'Too many'): - self.apply() - self.assertFalse((self.sdk / 'sdk/client/src').exists()) - - def test_rejects_oversized_total(self): - self.add_source(contents=b'123') - self.add_source('client/src/test/kotlin/Test.kt', contents=b'456') - with patch.object(extension_module, 'MAX_TOTAL_BYTES', 5): - with self.assertRaisesRegex(ValueError, 'total size'): - self.apply() - - def test_rejects_overlapping_roots(self): - with self.assertRaisesRegex(ValueError, 'must not overlap'): - extension_module.apply_extension(self.sdk / 'sdk', self.sdk) - - -if __name__ == '__main__': - unittest.main()