diff --git a/.github/workflows/dev-packages.yml b/.github/workflows/dev-packages.yml index 424d0e8a..47a8b951 100644 --- a/.github/workflows/dev-packages.yml +++ b/.github/workflows/dev-packages.yml @@ -50,20 +50,6 @@ jobs: - name: Run Pack For Common run: dotnet pack PowerSync/PowerSync.Common -c Release -o ${{ github.workspace }}/output - - name: Extract MAUI Package Version from CHANGELOG.md - shell: bash - run: | - MAUI_VERSION=$(awk '/^## [0-9]+\.[0-9]+\.[0-9]+-dev(\.[0-9]+)?$/ {print $2; exit}' PowerSync/PowerSync.Maui/CHANGELOG.md) - if [[ -z "$MAUI_VERSION" ]]; then - echo "Error: Invalid dev version found in PowerSync.Maui/CHANGELOG.md. Expected format: x.x.x-dev.x" - exit 1 - fi - echo "Detected Version: $MAUI_VERSION" - echo "VERSION=$MAUI_VERSION" >> $GITHUB_ENV - - - name: Run Pack For MAUI - run: dotnet pack PowerSync/PowerSync.Maui -c Release -o ${{ github.workspace }}/output - - name: Upload Build Artifacts uses: actions/upload-artifact@v4 with: @@ -158,9 +144,3 @@ jobs: dotnet nuget push "${{ github.workspace }}\SignedArtifacts\PowerSync.Common*.nupkg" --api-key "${{ steps.login.outputs.NUGET_API_KEY }}" --source https://api.nuget.org/v3/index.json - - - name: Run Push For Maui - run: > - dotnet nuget push "${{ github.workspace }}\SignedArtifacts\PowerSync.Maui*.nupkg" - --api-key "${{ steps.login.outputs.NUGET_API_KEY }}" - --source https://api.nuget.org/v3/index.json diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8847ae65..5cb2ea42 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,7 +15,6 @@ jobs: outputs: common_version: ${{ steps.extract_version.outputs.common_version }} - maui_version: ${{ steps.extract_maui_version.outputs.maui_version }} steps: - name: Checkout Repository @@ -56,22 +55,6 @@ jobs: - name: Run Pack For Common run: dotnet pack PowerSync/PowerSync.Common -c Release -o ${{ github.workspace }}/output - - name: Extract MAUI Package Version from CHANGELOG.md - id: extract_maui_version - shell: bash - run: | - MAUI_VERSION=$(awk '/^## [0-9]+\.[0-9]+\.[0-9]+/ {print $2; exit}' PowerSync/PowerSync.Maui/CHANGELOG.md) - if [[ -z "$MAUI_VERSION" ]]; then - echo "Error: Invalid version found in PowerSync.Maui/CHANGELOG.md. Expected format: '## x.x.x'" - exit 1 - fi - echo "Detected Version: $MAUI_VERSION" - echo "VERSION=$MAUI_VERSION" >> $GITHUB_ENV - echo "maui_version=$MAUI_VERSION" >> $GITHUB_OUTPUT - - - name: Run Pack For MAUI - run: dotnet pack PowerSync/PowerSync.Maui -c Release -o ${{ github.workspace }}/output - - name: Upload Build Artifacts uses: actions/upload-artifact@v4 with: @@ -182,23 +165,3 @@ jobs: --generate-notes \ SignedArtifacts/PowerSync.Common*.nupkg - - name: Run Push For Maui - run: > - dotnet nuget push "${{ github.workspace }}\SignedArtifacts\PowerSync.Maui*.nupkg" - --api-key "${{ steps.login.outputs.NUGET_API_KEY }}" - --source https://api.nuget.org/v3/index.json - - - name: Create GitHub Release For Maui - continue-on-error: true - shell: bash - env: - GH_TOKEN: ${{ github.token }} - GH_REPO: ${{ github.repository }} - MAUI_VERSION: ${{ needs.build.outputs.maui_version }} - run: | - gh release create "PowerSync.Maui@${MAUI_VERSION}" \ - --draft \ - --title "PowerSync.Maui@${MAUI_VERSION}" \ - --target main \ - --generate-notes \ - SignedArtifacts/PowerSync.Maui*.nupkg diff --git a/PowerSync/PowerSync.Common/CHANGELOG.md b/PowerSync/PowerSync.Common/CHANGELOG.md index 60a52328..4474ba64 100644 --- a/PowerSync/PowerSync.Common/CHANGELOG.md +++ b/PowerSync/PowerSync.Common/CHANGELOG.md @@ -1,5 +1,11 @@ # PowerSync.Common Changelog +## Unreleased + +- **Breaking:** `PowerSync.Maui` has been folded into `PowerSync.Common` and is no longer published. Mobile and desktop targets are now served by a single package. + - Remove the `PowerSync.Maui` package reference; `PowerSync.Common` alone is sufficient on all platforms. + - Replace `MAUISQLiteDBOpenFactory` with `MDSQLiteDBOpenFactory` (from `PowerSync.Common.MDSQLite`) and drop the `using PowerSync.Maui.SQLite;` import. `MAUISQLiteAdapter` is likewise replaced by `MDSQLiteAdapter`, which now selects the correct native extension per platform. + ## 0.1.4 - Update the PowerSync SQLite core extension to 0.5.2. diff --git a/PowerSync/PowerSync.Common/MDSQLite/MDSQLiteAdapter.cs b/PowerSync/PowerSync.Common/MDSQLite/MDSQLiteAdapter.cs index cdb4ec9e..8f4e0755 100644 --- a/PowerSync/PowerSync.Common/MDSQLite/MDSQLiteAdapter.cs +++ b/PowerSync/PowerSync.Common/MDSQLite/MDSQLiteAdapter.cs @@ -153,16 +153,41 @@ protected virtual void LoadExtensions(SqliteConnection db) } /// - /// Loads the bundled PowerSync core SQLite extension. Override on - /// platform-specific adapters (e.g. MAUI iOS/Android) where the native library - /// lives outside the desktop runtime path. + /// Loads the bundled PowerSync core SQLite extension. The extension ships in a + /// different form per platform: an xcframework on iOS/MacCatalyst, a jniLib on + /// Android, and a plain native library under runtimes/ on desktop. /// protected virtual void LoadDefaultPowerSyncExtension(SqliteConnection db) { +#if IOS || MACCATALYST + LoadAppleFrameworkExtension(db); +#elif ANDROID + db.LoadExtension("libpowersync"); +#else var path = PowerSyncPathResolver.GetNativeLibraryPath(AppContext.BaseDirectory); db.LoadExtension(path, "sqlite3_powersync_init"); +#endif } +#if IOS || MACCATALYST + private static void LoadAppleFrameworkExtension(SqliteConnection db) + { + var bundlePath = Foundation.NSBundle.FromIdentifier("co.powersync.sqlitecore")?.BundlePath; + if (bundlePath == null) + { + throw new Exception("Could not find PowerSync SQLite extension bundle path"); + } + + var filePath = Path.Combine(bundlePath, "powersync-sqlite-core"); + + using var loadExtension = db.CreateCommand(); + loadExtension.CommandText = "SELECT load_extension(@path, @entryPoint)"; + loadExtension.Parameters.AddWithValue("@path", filePath); + loadExtension.Parameters.AddWithValue("@entryPoint", "sqlite3_powersync_init"); + loadExtension.ExecuteNonQuery(); + } +#endif + public async Task Close() { tablesUpdatedCts?.Cancel(); diff --git a/PowerSync/PowerSync.Maui/build/ApiDefinition.cs b/PowerSync/PowerSync.Common/Platforms/ApiDefinition.cs similarity index 71% rename from PowerSync/PowerSync.Maui/build/ApiDefinition.cs rename to PowerSync/PowerSync.Common/Platforms/ApiDefinition.cs index 5fe8ef43..75d0dc6f 100644 --- a/PowerSync/PowerSync.Maui/build/ApiDefinition.cs +++ b/PowerSync/PowerSync.Common/Platforms/ApiDefinition.cs @@ -1,4 +1,4 @@ -namespace PowerSync.Maui.build +namespace PowerSync.Common.Platforms { // Empty API definition - allows xcframework to be included without managed bindings } diff --git a/PowerSync/PowerSync.Common/PowerSync.Common.csproj b/PowerSync/PowerSync.Common/PowerSync.Common.csproj index 6e45bb7d..ac52a4d4 100644 --- a/PowerSync/PowerSync.Common/PowerSync.Common.csproj +++ b/PowerSync/PowerSync.Common/PowerSync.Common.csproj @@ -20,6 +20,10 @@ NU5100 README.md $(DefaultItemExcludes);runtimes/**/*.*; + + true + true @@ -42,21 +46,45 @@ - - + - + PreserveNewest - - - - - + + + + + + + Framework + False + + + + + + + + + + diff --git a/PowerSync/PowerSync.Common/PowerSync.Common.targets b/PowerSync/PowerSync.Common/PowerSync.Common.targets index 0c5bcba2..219c2224 100644 --- a/PowerSync/PowerSync.Common/PowerSync.Common.targets +++ b/PowerSync/PowerSync.Common/PowerSync.Common.targets @@ -1,7 +1,22 @@ + + + + + - \ No newline at end of file + diff --git a/PowerSync/PowerSync.Common/README.md b/PowerSync/PowerSync.Common/README.md index 9526ecdf..4871acd7 100644 --- a/PowerSync/PowerSync.Common/README.md +++ b/PowerSync/PowerSync.Common/README.md @@ -2,6 +2,8 @@ This package contains a .NET implementation of a PowerSync database connector and streaming sync bucket implementation. +It bundles the native PowerSync SQLite extension for every supported target and loads the right one automatically, so no additional package is required on mobile. + ## ⚠️ Project Status & Release Note This package is in beta and is considered ready for production use for tested use cases. See our feature status definitions [here](https://docs.powersync.com/resources/feature-status). @@ -36,6 +38,26 @@ static async Task Main() { ``` +### MAUI / mobile + +Initialization is the same on mobile, except that the database file must be placed in a +platform-appropriate location. Supply an `MDSQLiteDBOpenFactory` built from that path: + +```csharp +var dbPath = Path.Combine(FileSystem.AppDataDirectory, "maui-example.db"); +var factory = new MDSQLiteDBOpenFactory(new MDSQLiteOpenFactoryOptions +{ + DbFilename = dbPath +}); + +var db = new PowerSyncDatabase(new PowerSyncDatabaseOptions +{ + Database = factory, + Schema = AppSchema.PowerSyncSchema, +}); +await db.Init(); +``` + ### Watched queries Watched queries will automatically update when a dependant table is updated. diff --git a/PowerSync/PowerSync.Maui/CHANGELOG.md b/PowerSync/PowerSync.Maui/CHANGELOG.md deleted file mode 100644 index a5a5ca77..00000000 --- a/PowerSync/PowerSync.Maui/CHANGELOG.md +++ /dev/null @@ -1,67 +0,0 @@ -# PowerSync.Maui Changelog - -## 0.1.4 - -- Upstream PowerSync.Common version bump (See Powersync.Common changelog 0.1.4 for more information) - -## 0.1.3 - -- Upstream PowerSync.Common version bump (See Powersync.Common changelog 0.1.3 for more information) - -## 0.1.2 - -- Add support for MacCatalyst. -- Add support for .NET 9.0. Supported targets now also include `net9.0`, `net9.0-android`, `net9.0-ios`, and `net9.0-maccatalyst`. -- Upstream PowerSync.Common version bump (See Powersync.Common changelog 0.1.2 for more information) - -## 0.1.1 - -- Upstream PowerSync.Common version bump (See Powersync.Common changelog 0.1.1 for more information) - -## 0.1.0 - -- Beta release. - -## 0.0.9-alpha.1 - -- Upstream PowerSync.Common version bump (See Powersync.Common changelog 0.0.11-alpha.1 for more information) - -## 0.0.8-alpha.1 - -- Upstream PowerSync.Common version bump (See Powersync.Common changelog 0.0.10-alpha.1 for more information) - -## 0.0.7-alpha.1 - -- Upstream PowerSync.Common version bump (See Powersync.Common changelog 0.0.9-alpha.1 for more information) - - _Breaking:_ Updates to how the application schema is defined. - -## 0.0.6-alpha.1 - -- Upstream PowerSync.Common version bump (See Powersync.Common changelog 0.0.8-alpha.1 for more information) - -## 0.0.5-alpha.1 - -- Upstream PowerSync.Common version bump (See Powersync.Common changelog 0.0.7-alpha.1 for more information) - -## 0.0.4-alpha.1 - -- Upstream PowerSync.Common version bump (See Powersync.Common changelog 0.0.6-alpha.1 for more information) -- Added ability to specify `AppMetadata` sync/stream requests (see Common changelog). - -## 0.0.3-alpha.1 - -- Upstream PowerSync.Common version bump (See PowerSync.Common change 0.0.5-alpha.1 for more information) -- Using the latest (0.4.9) version of the core extension, it introduces support for the Rust Sync implementation and also makes it the default - users can still opt out and use the legacy C# sync implementation as option when calling `connect()`. - -## 0.0.2-alpha.1 - -- Fixed issues related to extension loading when installing package outside of the monorepo. - -## 0.0.1-alpha.1 - -- Introduce package. Support for iOS/Android use cases. - -### Platform Runtime Support Added - -- MAUI iOS -- MAUI Android diff --git a/PowerSync/PowerSync.Maui/PowerSync.Maui.csproj b/PowerSync/PowerSync.Maui/PowerSync.Maui.csproj deleted file mode 100644 index 74e9a948..00000000 --- a/PowerSync/PowerSync.Maui/PowerSync.Maui.csproj +++ /dev/null @@ -1,67 +0,0 @@ - - - - netstandard2.0;net6.0;net8.0;net9.0;net8.0-ios;net8.0-android;net8.0-maccatalyst;net9.0-ios;net9.0-android;net9.0-maccatalyst - 12 - enable - enable - PowerSync.Maui - PowerSync.Maui - PowerSync.Maui is a package that enables MAUI usage for PowerSync - PowerSync - powersync - Apache-2.0 - https://github.com/powersync-ja/powersync-dotnet - https://powersync.com - true - https://github.com/powersync-ja/powersync-dotnet/PowerSync/PowerSync.Maui/CHANGELOG.md - powersync local-first local-storage state-management offline sql db persistence sqlite sync - icon.png - NU5100 - README.md - true - true - - - - - - - - - - - Designer - - - - - - - - - Framework - False - - - - - - - - Framework - False - - - - - - - - - - - diff --git a/PowerSync/PowerSync.Maui/README.md b/PowerSync/PowerSync.Maui/README.md deleted file mode 100644 index bc75882c..00000000 --- a/PowerSync/PowerSync.Maui/README.md +++ /dev/null @@ -1,45 +0,0 @@ -# PowerSync SDK .NET MAUI - -This package provides .NET Multi-platform App UI (MAUI) integration for PowerSync, designed to work with the PowerSync.Common package for cross-platform mobile and desktop applications. - -## ⚠️ Project Status & Release Note - -This package is in beta and is considered ready for production use for tested use cases. See our feature status definitions [here](https://docs.powersync.com/resources/feature-status). - -## Installation - -This package is published on [NuGet](https://www.nuget.org/packages/PowerSync.Maui) and requires PowerSync.Common to also be installed. - -```bash -dotnet add package PowerSync.Maui -dotnet add package PowerSync.Common -``` - -## Usage - -Initialization differs slightly from our Common SDK when using MAUI. - -```csharp - -private record ListResult(string id, string name, string owner_id, string created_at); - -static async Task Main() { - - // Ensures the DB file is stored in a platform appropriate location - var dbPath = Path.Combine(FileSystem.AppDataDirectory, "maui-example.db"); - var factory = new MAUISQLiteDBOpenFactory(new MDSQLiteOpenFactoryOptions() - { - DbFilename = dbPath - }); - - var Db = new PowerSyncDatabase(new PowerSyncDatabaseOptions() - { - Database = factory, // Supply a factory - Schema = AppSchema.PowerSyncSchema, - }); - - await db.Init(); - - var lists = await db.GetAll("select * from lists"); -} -``` diff --git a/PowerSync/PowerSync.Maui/SQLite/MAUISQLiteAdapter.cs b/PowerSync/PowerSync.Maui/SQLite/MAUISQLiteAdapter.cs deleted file mode 100644 index 0d6e3993..00000000 --- a/PowerSync/PowerSync.Maui/SQLite/MAUISQLiteAdapter.cs +++ /dev/null @@ -1,55 +0,0 @@ -namespace PowerSync.Maui.SQLite; - -using Microsoft.Data.Sqlite; - -using PowerSync.Common.MDSQLite; - -// iOS/MacCatalyst specific imports -#if IOS || MACCATALYST -using Foundation; -#endif - -public class MAUISQLiteAdapter : MDSQLiteAdapter -{ - public MAUISQLiteAdapter(MDSQLiteAdapterOptions options) : base(options) - { - } - - // The bundled PowerSync extension lives in a platform-specific location on - // iOS/MacCatalyst/Android — the desktop runtime path used by the base class - // does not resolve to it. Override only the PowerSync-extension load hook; - // user-supplied custom extensions still flow through MDSQLiteAdapter.LoadExtensions - // unchanged, so consumers can freely combine the bundled extension (via the - // LoadPowerSyncExtension flag) with their own. - protected override void LoadDefaultPowerSyncExtension(SqliteConnection db) - { -#if IOS || MACCATALYST - LoadExtensionApple(db); -#elif ANDROID - db.LoadExtension("libpowersync"); -#else - base.LoadDefaultPowerSyncExtension(db); -#endif - } - - private static void LoadExtensionApple(SqliteConnection db) - { -#if IOS || MACCATALYST - var bundlePath = Foundation.NSBundle.FromIdentifier("co.powersync.sqlitecore")?.BundlePath; - if (bundlePath == null) - { - throw new Exception("Could not find PowerSync SQLite extension bundle path"); - } - - var filePath = - Path.Combine(bundlePath, "powersync-sqlite-core"); - - using var loadExtension = db.CreateCommand(); - loadExtension.CommandText = "SELECT load_extension(@path, @entryPoint)"; - loadExtension.Parameters.AddWithValue("@path", filePath); - loadExtension.Parameters.AddWithValue("@entryPoint", "sqlite3_powersync_init"); - loadExtension.ExecuteNonQuery(); -#endif - } -} - diff --git a/PowerSync/PowerSync.Maui/SQLite/MAUISQLiteDBOpenFactory.cs b/PowerSync/PowerSync.Maui/SQLite/MAUISQLiteDBOpenFactory.cs deleted file mode 100644 index c8d35088..00000000 --- a/PowerSync/PowerSync.Maui/SQLite/MAUISQLiteDBOpenFactory.cs +++ /dev/null @@ -1,26 +0,0 @@ -using PowerSync.Common.DB; - -namespace PowerSync.Maui.SQLite; - -using PowerSync.Common.Client; -using PowerSync.Common.MDSQLite; - - -public class MAUISQLiteDBOpenFactory : ISQLOpenFactory -{ - private readonly MDSQLiteOpenFactoryOptions options; - - public MAUISQLiteDBOpenFactory(MDSQLiteOpenFactoryOptions options) - { - this.options = options; - } - - public IDBAdapter OpenDatabase() - { - return new MAUISQLiteAdapter(new MDSQLiteAdapterOptions - { - Name = options.DbFilename, - SqliteOptions = options.SqliteOptions - }); - } -} diff --git a/README.md b/README.md index f71ef329..0832d5ce 100644 --- a/README.md +++ b/README.md @@ -13,9 +13,7 @@ _[PowerSync](https://www.powersync.com) is a sync engine for building local-firs Packages are published to [NuGet](https://www.nuget.org/profiles/PowerSync). - [PowerSync.Common](./PowerSync/PowerSync.Common/README.md) - - Core package: .NET implementation of a PowerSync database connector and streaming sync bucket implementation. Packages meant for specific platforms will extend functionality of `Common`. -- [PowerSync.Maui](./PowerSync/PowerSync.Maui/README.md) - - Extends the PowerSync.Common package to provide the .NET Multi-platform App UI (MAUI) integration for PowerSync for cross-platform mobile and desktop applications. + - The only package you need: .NET implementation of a PowerSync database connector and streaming sync bucket implementation. It bundles the native PowerSync SQLite extension for every supported target — desktop (Windows, macOS, Linux), iOS, Android and MacCatalyst — and selects the right one automatically. ## Demo Apps / Example Projects diff --git a/RELEASE.md b/RELEASE.md index 6beed425..5845dfe2 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -4,14 +4,13 @@ 1. Ensure all changes you want to release are merged into `main`. -2. Ensure the changelog files for both [Common](./PowerSync/PowerSync.Common/CHANGELOG.md) and [Maui](./PowerSync/PowerSync.Common/CHANGELOG.md) have been updated with _new, well-formatted version numbers._ - - The [release workflow](./.github/workflows/release.yml) obtains the version number by searching for the top-most version line (prefixed with `## `), stripping the prefix, and taking the remaining text as the version number. It does this for both changelogs. - - If a release only includes changes to `PowerSync.Common`, don't forget to also update `PowerSync.Maui`'s changelog so that a new release is created that uses the updated version of `PowerSync.Common`. +2. Ensure the [Common changelog](./PowerSync/PowerSync.Common/CHANGELOG.md) has been updated with a _new, well-formatted version number._ + - The [release workflow](./.github/workflows/release.yml) obtains the version number by searching for the top-most version line (prefixed with `## `), stripping the prefix, and taking the remaining text as the version number. - By convention, we generally update the changelog within the PRs that add the changes. This sometimes leads to the version number in the changelog being a version ahead of the released version (eg. latest released version is v0.0.3, but changelog has v0.0.4.) 3. Run the `Release` workflow on Github. This will: - - Extract version numbers from the changelogs. - - Create a release for both packages on Nuget. + - Extract the version number from the changelog. + - Create a release for the package on Nuget. - Create a Github release containing the changelog contents. ## Dev Releases diff --git a/Tools/Setup/Setup.cs b/Tools/Setup/Setup.cs index c91d2ecd..795f86ee 100644 --- a/Tools/Setup/Setup.cs +++ b/Tools/Setup/Setup.cs @@ -1,9 +1,13 @@ using System; using System.IO; +using System.Linq; using System.Net.Http; using System.Threading.Tasks; using System.Collections.Generic; using System.IO.Compression; +using System.Text; +using System.Xml; +using System.Xml.Linq; /// /// Execute with `dotnet run --project Tools/Setup` @@ -14,6 +18,17 @@ public class PowerSyncSetup private const string GITHUB_BASE_URL = $"https://github.com/powersync-ja/powersync-sqlite-core/releases/download/v{VERSION}"; + /// + /// xcframework slices reachable from our target frameworks. Everything else is removed + /// by . + /// + private static readonly string[] REQUIRED_APPLE_SLICES = + [ + "ios-arm64", // device + "ios-arm64_x86_64-simulator", // simulator + "ios-arm64_x86_64-maccatalyst" // MacCatalyst + ]; + private readonly HttpClient _httpClient; private readonly string _basePath; @@ -28,9 +43,8 @@ public async Task RunSetup() try { await SetupDesktop(); - await SetupMauiIos(); - await SetupMauiAndroid(); - await SetupMauiMacCatalyst(); + await SetupApple(); + await SetupAndroid(); } finally { @@ -89,24 +103,130 @@ private async Task ProcessDesktopRuntime(string basePath, KeyValuePair + /// Downloads the xcframework used by the iOS and MacCatalyst targets. The archive is + /// self-describing and contains slices for both, so a single copy serves both targets. + /// + public async Task SetupApple() { - Console.WriteLine("Setting up MAUI iOS libraries..."); + Console.WriteLine("Setting up Apple libraries..."); - var nativeDir = Path.Combine(_basePath, "PowerSync.Maui", "Platforms", "iOS", "NativeLibs"); + var nativeDir = Path.Combine(_basePath, "PowerSync.Common", "Platforms", "Apple", "NativeLibs"); var config = new ArchiveConfig( "powersync-sqlite-core.xcframework.zip", "powersync-sqlite-core.xcframework" ); await ProcessArchiveDownload(nativeDir, config, GITHUB_BASE_URL); + + var xcframeworkPath = Path.Combine(nativeDir, config.ExtractedName); + if (Directory.Exists(xcframeworkPath)) + { + TrimXcframework(xcframeworkPath); + } } - public async Task SetupMauiAndroid() + /// + /// Removes xcframework slices and debug symbols that none of our target frameworks can + /// use. The upstream archive also ships tvOS, watchOS and native macOS slices plus dSYMs + /// for every slice; together these are ~97% of its size, and they would otherwise be + /// embedded into the NuGet package four times over (net8/net9 x ios/maccatalyst). + /// + private static void TrimXcframework(string xcframeworkPath) { - Console.WriteLine("Setting up MAUI Android libraries..."); + var infoPlistPath = Path.Combine(xcframeworkPath, "Info.plist"); + + // Parse the DOCTYPE (so Save writes it back) without fetching the external DTD. + var settings = new XmlReaderSettings { DtdProcessing = DtdProcessing.Parse, XmlResolver = null }; + XDocument plist; + using (var reader = XmlReader.Create(infoPlistPath, settings)) + { + plist = XDocument.Load(reader); + } - var nativeDir = Path.Combine(_basePath, "PowerSync.Maui", "Platforms", "Android", "jniLibs"); + var availableLibraries = plist.Descendants("key") + .First(key => key.Value == "AvailableLibraries") + .ElementsAfterSelf("array") + .First(); + + var keptSlices = new List(); + + foreach (var slice in availableLibraries.Elements("dict").ToList()) + { + var identifier = PlistValue(slice, "LibraryIdentifier") + ?? throw new Exception("xcframework slice has no LibraryIdentifier"); + var sliceDir = Path.Combine(xcframeworkPath, identifier); + + if (!REQUIRED_APPLE_SLICES.Contains(identifier)) + { + DeleteDirectoryIfExists(sliceDir); + slice.Remove(); + continue; + } + + var debugSymbolsPath = PlistValue(slice, "DebugSymbolsPath"); + if (debugSymbolsPath != null) + { + DeleteDirectoryIfExists(Path.Combine(sliceDir, debugSymbolsPath)); + RemovePlistEntry(slice, "DebugSymbolsPath"); + } + + keptSlices.Add(identifier); + } + + // Fail loudly rather than shipping a package that silently lost a platform, in case + // upstream renames a slice. + var missing = REQUIRED_APPLE_SLICES.Except(keptSlices).ToList(); + if (missing.Count > 0) + { + throw new Exception($"xcframework is missing required slice(s): {string.Join(", ", missing)}"); + } + + // XDocument otherwise round-trips the empty internal DTD subset as "[]", and prepends + // a BOM. plutil rejects both. + if (plist.DocumentType != null) + { + plist.DocumentType.InternalSubset = null; + } + + var writerSettings = new XmlWriterSettings + { + Indent = true, + Encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false), + }; + using (var writer = XmlWriter.Create(infoPlistPath, writerSettings)) + { + plist.Save(writer); + } + + Console.WriteLine($"✓ Trimmed xcframework to: {string.Join(", ", keptSlices)}"); + } + + private static string? PlistValue(XElement dict, string key) => + dict.Elements("key") + .FirstOrDefault(element => element.Value == key) + ?.ElementsAfterSelf().FirstOrDefault()?.Value; + + private static void RemovePlistEntry(XElement dict, string key) + { + var keyElement = dict.Elements("key").First(element => element.Value == key); + keyElement.ElementsAfterSelf().First().Remove(); + keyElement.Remove(); + } + + private static void DeleteDirectoryIfExists(string path) + { + if (Directory.Exists(path)) + { + Directory.Delete(path, recursive: true); + } + } + + public async Task SetupAndroid() + { + Console.WriteLine("Setting up Android libraries..."); + + var nativeDir = Path.Combine(_basePath, "PowerSync.Common", "Platforms", "Android", "jniLibs"); try { @@ -135,19 +255,6 @@ private async Task DownloadAndroidLibrary(string filename, string jniLibsDir, st await DownloadFile($"{GITHUB_BASE_URL}/{filename}", targetFile); } - public async Task SetupMauiMacCatalyst() - { - Console.WriteLine("Setting up MAUI MacCatalyst libraries..."); - - var nativeDir = Path.Combine(_basePath, "PowerSync.Maui", "Platforms", "MacCatalyst", "NativeLibs"); - var config = new ArchiveConfig( - "powersync-sqlite-core.xcframework.zip", - "powersync-sqlite-core.xcframework" - ); - - await ProcessArchiveDownload(nativeDir, config, GITHUB_BASE_URL); - } - private async Task ProcessArchiveDownload(string nativeDir, ArchiveConfig config, string baseUrl) { try diff --git a/demos/MAUITodo/Data/PowerSyncData.cs b/demos/MAUITodo/Data/PowerSyncData.cs index 676781f8..818e20c6 100644 --- a/demos/MAUITodo/Data/PowerSyncData.cs +++ b/demos/MAUITodo/Data/PowerSyncData.cs @@ -8,7 +8,6 @@ using PowerSync.Common.Attachments; using PowerSync.Common.Client; using PowerSync.Common.MDSQLite; -using PowerSync.Maui.SQLite; namespace MAUITodo.Data; @@ -29,7 +28,7 @@ public PowerSyncData() var logger = loggerFactory.CreateLogger("PowerSyncLogger"); var dbPath = Path.Combine(FileSystem.AppDataDirectory, "example.db"); - var factory = new MAUISQLiteDBOpenFactory(new MDSQLiteOpenFactoryOptions() + var factory = new MDSQLiteDBOpenFactory(new MDSQLiteOpenFactoryOptions() { DbFilename = dbPath }); diff --git a/demos/MAUITodo/MAUITodo.csproj b/demos/MAUITodo/MAUITodo.csproj index 56d224b4..21684022 100644 --- a/demos/MAUITodo/MAUITodo.csproj +++ b/demos/MAUITodo/MAUITodo.csproj @@ -72,6 +72,5 @@ - diff --git a/root.sln b/root.sln index 072d04eb..1519e87c 100644 --- a/root.sln +++ b/root.sln @@ -21,8 +21,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MAUITodo", "demos\MAUITodo\ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WPF", "demos\WPF\WPF.csproj", "{AF297026-0BEA-4B8E-97C9-6540C6D52B36}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PowerSync.Maui", "PowerSync\PowerSync.Maui\PowerSync.Maui.csproj", "{A4A91B9F-0C86-41CB-BEF0-C002819C43BE}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PowerSync.Common.IntegrationTests", "Tests\PowerSync\PowerSync.Common.IntegrationTests\PowerSync.Common.IntegrationTests.csproj", "{EB81D453-777D-40B5-A504-4144906ADBF4}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PowerSync.Common.PerformanceTests", "Tests\PowerSync\PowerSync.Common.PerformanceTests\PowerSync.Common.PerformanceTests.csproj", "{B2BA37D6-0549-455D-9520-B3D21C7876D0}" @@ -97,18 +95,6 @@ Global {AF297026-0BEA-4B8E-97C9-6540C6D52B36}.Release|x64.Build.0 = Release|Any CPU {AF297026-0BEA-4B8E-97C9-6540C6D52B36}.Release|x86.ActiveCfg = Release|Any CPU {AF297026-0BEA-4B8E-97C9-6540C6D52B36}.Release|x86.Build.0 = Release|Any CPU - {A4A91B9F-0C86-41CB-BEF0-C002819C43BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A4A91B9F-0C86-41CB-BEF0-C002819C43BE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A4A91B9F-0C86-41CB-BEF0-C002819C43BE}.Debug|x64.ActiveCfg = Debug|Any CPU - {A4A91B9F-0C86-41CB-BEF0-C002819C43BE}.Debug|x64.Build.0 = Debug|Any CPU - {A4A91B9F-0C86-41CB-BEF0-C002819C43BE}.Debug|x86.ActiveCfg = Debug|Any CPU - {A4A91B9F-0C86-41CB-BEF0-C002819C43BE}.Debug|x86.Build.0 = Debug|Any CPU - {A4A91B9F-0C86-41CB-BEF0-C002819C43BE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A4A91B9F-0C86-41CB-BEF0-C002819C43BE}.Release|Any CPU.Build.0 = Release|Any CPU - {A4A91B9F-0C86-41CB-BEF0-C002819C43BE}.Release|x64.ActiveCfg = Release|Any CPU - {A4A91B9F-0C86-41CB-BEF0-C002819C43BE}.Release|x64.Build.0 = Release|Any CPU - {A4A91B9F-0C86-41CB-BEF0-C002819C43BE}.Release|x86.ActiveCfg = Release|Any CPU - {A4A91B9F-0C86-41CB-BEF0-C002819C43BE}.Release|x86.Build.0 = Release|Any CPU {EB81D453-777D-40B5-A504-4144906ADBF4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {EB81D453-777D-40B5-A504-4144906ADBF4}.Debug|Any CPU.Build.0 = Debug|Any CPU {EB81D453-777D-40B5-A504-4144906ADBF4}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -144,7 +130,6 @@ Global {D7FDA714-D29F-4D85-B3F2-74C6810A36F9} = {9144195A-C68F-4B1E-A574-474EDD424D6C} {B8B2A9E2-FEC9-495B-B03E-23078E7B651D} = {9144195A-C68F-4B1E-A574-474EDD424D6C} {AF297026-0BEA-4B8E-97C9-6540C6D52B36} = {9144195A-C68F-4B1E-A574-474EDD424D6C} - {A4A91B9F-0C86-41CB-BEF0-C002819C43BE} = {B1D87BA9-8812-4EFA-BBBE-1FF1EEEB5433} {EB81D453-777D-40B5-A504-4144906ADBF4} = {C784FBE4-CC1E-4A0A-AE8E-6B818DD3724D} {B2BA37D6-0549-455D-9520-B3D21C7876D0} = {C784FBE4-CC1E-4A0A-AE8E-6B818DD3724D} EndGlobalSection