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/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 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()