diff --git a/packages/komodo_defi_framework/app_build/BUILD_CONFIG_README.md b/packages/komodo_defi_framework/app_build/BUILD_CONFIG_README.md index 09a658a7..595d56de 100644 --- a/packages/komodo_defi_framework/app_build/BUILD_CONFIG_README.md +++ b/packages/komodo_defi_framework/app_build/BUILD_CONFIG_README.md @@ -40,6 +40,39 @@ Paths in the config are relative to that package directory. - Use `--concurrent` for faster downloads in development - Override behavior per build via env `OVERRIDE_DEFI_API_DOWNLOAD=true|false` +### Using Nebula mirror and short commit hashes + +- You can add Nebula as an additional source in `api.source_urls`: + +``` +"source_urls": [ + "https://api.github.com/repos/KomodoPlatform/komodo-defi-framework", + "https://sdk.devbuilds.komodo.earth/", + "https://nebula.decker.im/kdf/" +] +``` + +- The downloader expects branch-scoped directory listings (e.g., `.../dev/`) on both devbuilds and Nebula mirrors and will fallback to the base listing when available. It searches for artifacts that match the platform patterns and contain either the full commit hash or a 7-char short hash. +- To pin a specific commit (e.g., `4025b8c`) without changing branches, update `api.api_commit_hash` or use the CLI with `--commit`: + +```bash +dart run packages/komodo_wallet_cli/bin/update_api_config.dart \ + --source mirror \ + --mirror-url https://nebula.decker.im/ \ + --commit 4025b8c \ + --config packages/komodo_defi_framework/app_build/build_config.json \ + --output-dir packages/komodo_defi_framework/app_build/temp_downloads \ + --verbose +``` + +- To switch to a different Nebula commit in the future, either: + - Edit `api.api_commit_hash` in `build_config.json` to the new short/full hash, or + - Re-run the CLI with a different `--commit ` value. + +Notes: +- Nebula index includes additional files like `komodo-wallet-*`; these are automatically ignored by the downloader. +- macOS on Nebula uses `kdf-macos-universal2-.zip` (special case handled in `matching_pattern`). Other platforms use `kdf_-.zip`. + ## Troubleshooting - Missing files: verify `config_output_path` points to this folder and the file exists diff --git a/packages/komodo_defi_framework/app_build/build_config.json b/packages/komodo_defi_framework/app_build/build_config.json index fac174bf..27f8ae5c 100644 --- a/packages/komodo_defi_framework/app_build/build_config.json +++ b/packages/komodo_defi_framework/app_build/build_config.json @@ -1,60 +1,72 @@ { "api": { - "api_commit_hash": "476262f0d3390e24cfe672d5e15a78a46397dbfa", - "branch": "hotfix-remove-memorydb-size-metric", + "api_commit_hash": "8beb1196487550157f5616bf27808cb6d4ebf275", + "branch": "staging", "fetch_at_build_enabled": true, "concurrent_downloads_enabled": true, "source_urls": [ "https://api.github.com/repos/KomodoPlatform/komodo-defi-framework", - "https://sdk.devbuilds.komodo.earth/" + "https://sdk.devbuilds.komodo.earth/", + "https://nebula.decker.im/" ], "platforms": { "web": { "matching_pattern": "^(?:kdf_[a-f0-9]{7,40}-wasm|mm2_[a-f0-9]{7,40}-wasm|mm2-[a-f0-9]{7,40}-wasm)\\.zip$", "valid_zip_sha256_checksums": [ - "1ce88c3ef6ee4da3839b392e77d9f667a897c4b0b9f4459f878a1561193541a7" + "d7cfd4dfa47f455ee283aa02c4dd11542b4bcf349928ec5b07a1f20b7141bd65", + "d1ce6fa6e6dd2d11135fc054b802dc1aea4772af2ae5aef9acc4c3da6ffacd35" ], "path": "web/kdf/bin" }, "ios": { "matching_pattern": "^(?:kdf_[a-f0-9]{7,40}-ios-aarch64|mm2_[a-f0-9]{7,40}-ios-aarch64|mm2-[a-f0-9]{7,40}-ios-aarch64-CI)\\.zip$", "valid_zip_sha256_checksums": [ - "68569c2bb3362dc94dd54b0e696559962fab8b29bd1203dd252078aeb1863ca3" + "49c26280232fadbe456fbdd1dacc2cfd7697272202e59e41612691e50376a347", + "57c77787ccab9cb0b7c54cfa0791a30210aa24a8548e66510d5871a6cae0b8a5" ], "path": "ios" }, "macos": { - "matching_pattern": "^(?:kdf_[a-f0-9]{7,40}-mac-arm64|mm2-[a-f0-9]{7,40}-Darwin-Release)\\.zip$", + "matching_pattern": "^(?:kdf_[a-f0-9]{7,40}-mac-arm64|kdf-macos-universal2-[a-f0-9]{7,40}|mm2-[a-f0-9]{7,40}-Darwin-Release)\\.zip$", + "matching_preference": [ + "universal2", + "mac-arm64" + ], "valid_zip_sha256_checksums": [ - "9613497ec79dab437251fb0c1ca6301c1b389325a01a8a979fde690d975c9902" + "30550ace9e66a1303ce3295ccd14f73759ff8fb56396419bd2d1d6e2da2cf54e", + "595de272b14ac52c6ee71ab442d355fc7f37b4a92f09198b575e5c44b796393f" ], "path": "macos/bin" }, "windows": { "matching_pattern": "^(?:kdf_[a-f0-9]{7,40}-win-x86-64|mm2_[a-f0-9]{7,40}-win-x86-64|mm2-[a-f0-9]{7,40}-Win64)\\.zip$", "valid_zip_sha256_checksums": [ - "d25b417ef17685e439f6be9f1100dfa46b5e9b3ef646037066a5b5db0852f107" + "db77335c8637d03e467c2cbb275ea4e57962ca22ed29542941f7105e5ba69e9a", + "619816f62bfd984abf0c2fb83d9efc3fea60b7a305fd912df0ee6c46ab376135" ], "path": "windows/bin" }, "android-armv7": { "matching_pattern": "^(?:kdf_[a-f0-9]{7,40}-android-armv7|mm2_[a-f0-9]{7,40}-android-armv7|mm2-[a-f0-9]{7,40}-android-armv7-CI)\\.zip$", "valid_zip_sha256_checksums": [ - "92e0d9515d3fb662bf90dc37802f901944d99b3c4b8e41566e6defacd59e213c" + "129f6f2467f14c9b7d9f7629d33d70197cb94b2237cbdb01a58e567a1b3260ee", + "5a393ce8dfb888d9edc3e4b24f42ac7f5f6f379cc14d9ac132f8cff37ab566d6" ], "path": "android/app/src/main/cpp/libs/armeabi-v7a" }, "android-aarch64": { "matching_pattern": "^(?:kdf_[a-f0-9]{7,40}-android-aarch64|mm2_[a-f0-9]{7,40}-android-aarch64|mm2-[a-f0-9]{7,40}-android-aarch64-CI)\\.zip$", "valid_zip_sha256_checksums": [ - "4d6dc94ba31087180df09136c51bc61c75fd217e77889eb45ef40e0c6a34537f" + "00ea932edb7750c60c5d91705907b52ab018a7230f270a0f4286e41ee42cdf74", + "c0ee3ea7a6fe8f4410208b9b841fd90ebdf6a28b30696ead6c1d8c5dc4c0b13d" ], "path": "android/app/src/main/cpp/libs/arm64-v8a" }, "linux": { "matching_pattern": "^(?:kdf_[a-f0-9]{7,40}-linux-x86-64|mm2_[a-f0-9]{7,40}-linux-x86-64|mm2-[a-f0-9]{7,40}-Linux-Release)\\.zip$", "valid_zip_sha256_checksums": [ - "7ab3d56bd0ad56e383903e3f0a444369f67b7945661f6fd0076a96b8d795cfbb" + "e25067ddb7b51455a109874722a595d7bc2ee4243ebd3af3e98426874d42f23d", + "93aa902af205fc2781c31e90e91688fff5aa63d9a26d77a21b1f95d934cccf75" ], "path": "linux/bin" } @@ -63,7 +75,7 @@ "coins": { "fetch_at_build_enabled": true, "update_commit_on_build": true, - "bundled_coins_repo_commit": "f235190081e821996333c95e34b2629df331b333", + "bundled_coins_repo_commit": "fdc6c7312b2c7da1b4b904103ed30445311072eb", "coins_repo_api_url": "https://api.github.com/repos/KomodoPlatform/coins", "coins_repo_content_url": "https://raw.githubusercontent.com/KomodoPlatform/coins", "coins_repo_branch": "master", diff --git a/packages/komodo_defi_framework/macos/komodo_defi_framework.podspec b/packages/komodo_defi_framework/macos/komodo_defi_framework.podspec index 4f1f25b4..13cdad6b 100644 --- a/packages/komodo_defi_framework/macos/komodo_defi_framework.podspec +++ b/packages/komodo_defi_framework/macos/komodo_defi_framework.podspec @@ -60,6 +60,55 @@ A new Flutter FFI plugin project. echo "Warning: libkdflib.dylib not found in lib/libkdflib.dylib" fi + # Prune binary slices to match $ARCHS (preserve universals) in Release builds only + if [ "$CONFIGURATION" = "Release" ]; then + TARGET_ARCHS="${ARCHS:-$(arch)}" + + thin_binary_to_archs() { + file="$1" + keep_archs="$2" + + [ -f "$file" ] || return 0 + + # Only act on fat files (multi-arch) + if ! lipo -info "$file" | grep -q 'Architectures in the fat file'; then + return 0 + fi + + bin_archs="$(lipo -archs "$file" 2>/dev/null || true)" + [ -n "$bin_archs" ] || return 0 + + dir="$(dirname "$file")" + base="$(basename "$file")" + work="$file" + + for arch in $bin_archs; do + echo "$keep_archs" | tr ' ' '\n' | grep -qx "$arch" && continue + echo "Removing architecture $arch from $base" + next="$(mktemp "$dir/.${base}.XXXXXX")" + lipo "$work" -remove "$arch" -output "$next" + [ "$work" != "$file" ] && rm -f "$work" + work="$next" + done + + if [ "$work" != "$file" ]; then + mv -f "$work" "$file" + fi + } + + thin_binary_to_archs "$APP_SUPPORT_DIR/kdf" "$TARGET_ARCHS" + if [ -f "$APP_SUPPORT_DIR/kdf" ]; then chmod +x "$APP_SUPPORT_DIR/kdf"; fi + + thin_binary_to_archs "$FRAMEWORKS_DIR/libkdflib.dylib" "$TARGET_ARCHS" + if [ -f "$FRAMEWORKS_DIR/libkdflib.dylib" ]; then install_name_tool -id "@rpath/libkdflib.dylib" "$FRAMEWORKS_DIR/libkdflib.dylib"; fi + + # Re-sign after modifications (best-effort) + if [ -n "$EXPANDED_CODE_SIGN_IDENTITY" ]; then + codesign --force --sign "$EXPANDED_CODE_SIGN_IDENTITY" "$APP_SUPPORT_DIR/kdf" 2>/dev/null || true + codesign --force --sign "$EXPANDED_CODE_SIGN_IDENTITY" "$FRAMEWORKS_DIR/libkdflib.dylib" 2>/dev/null || true + fi + fi + # Fail if neither file was found if [ $FOUND_REQUIRED_FILE -eq 0 ]; then echo "Error: Neither kdf executable nor libkdflib.dylib was found. At least one is required." @@ -71,7 +120,7 @@ A new Flutter FFI plugin project. # Configuration for macOS build s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', - 'EXCLUDED_ARCHS[sdk=macosx*]' => 'i386 x86_64', + # Allow building universal macOS apps (arm64 + x86_64). i386 remains excluded by default Xcode settings. 'OTHER_LDFLAGS' => '-framework SystemConfiguration', # Add rpath to ensure dylib can be found at runtime 'LD_RUNPATH_SEARCH_PATHS' => [ diff --git a/packages/komodo_defi_framework/macos/komodo_defi_framework.podspec.staticlib b/packages/komodo_defi_framework/macos/komodo_defi_framework.podspec.staticlib index 973fdccd..24fe081b 100644 --- a/packages/komodo_defi_framework/macos/komodo_defi_framework.podspec.staticlib +++ b/packages/komodo_defi_framework/macos/komodo_defi_framework.podspec.staticlib @@ -32,7 +32,7 @@ A new Flutter FFI plugin project. # s.pod_target_xcconfig = { "OTHER_LDFLAGS" => "$(inherited) -force_load $(PODS_TARGET_SRCROOT)/Frameworks/libkdflib.a -lstdc++" } s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', - 'EXCLUDED_ARCHS[sdk=macosx*]' => 'i386 x86_64', + # Allow building universal macOS apps (arm64 + x86_64). i386 remains excluded by default Xcode settings. 'OTHER_LDFLAGS' => '-force_load $(PODS_TARGET_SRCROOT)/Frameworks/libkdflib.a -lstdc++ -framework SystemConfiguration' } diff --git a/packages/komodo_defi_sdk/example/macos/Podfile.lock b/packages/komodo_defi_sdk/example/macos/Podfile.lock index e417bec1..2172c0f7 100644 --- a/packages/komodo_defi_sdk/example/macos/Podfile.lock +++ b/packages/komodo_defi_sdk/example/macos/Podfile.lock @@ -51,7 +51,7 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: flutter_secure_storage_darwin: ce237a8775b39723566dc72571190a3769d70468 FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1 - komodo_defi_framework: 725599127b357521f4567b16192bf07d7ad1d4b0 + komodo_defi_framework: 2b0389a26ed9c574c3665b257bcb3ef147fe5345 local_auth_darwin: d2e8c53ef0c4f43c646462e3415432c4dab3ae19 mobile_scanner: 9157936403f5a0644ca3779a38ff8404c5434a93 path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 diff --git a/packages/komodo_defi_sdk/example/macos/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/komodo_defi_sdk/example/macos/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 00000000..0c67376e --- /dev/null +++ b/packages/komodo_defi_sdk/example/macos/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/komodo_wallet_build_transformer/lib/src/steps/defi_api_build_step/dev_builds_artefact_downloader.dart b/packages/komodo_wallet_build_transformer/lib/src/steps/defi_api_build_step/dev_builds_artefact_downloader.dart index d1b8578d..eea04433 100644 --- a/packages/komodo_wallet_build_transformer/lib/src/steps/defi_api_build_step/dev_builds_artefact_downloader.dart +++ b/packages/komodo_wallet_build_transformer/lib/src/steps/defi_api_build_step/dev_builds_artefact_downloader.dart @@ -30,42 +30,84 @@ class DevBuildsArtefactDownloader implements ArtefactDownloader { ApiFileMatchingConfig matchingConfig, String platform, ) async { - final url = '$sourceUrl/$apiBranch/'; - final response = await http.get(Uri.parse(url)); - response.throwIfNotSuccessResponse(); + // Try both branch-scoped and base-scoped listings to support different mirrors + final normalizedSource = sourceUrl.endsWith('/') + ? sourceUrl + : '$sourceUrl/'; + final baseUri = Uri.parse(normalizedSource); + final candidateListingUrls = { + if (apiBranch.isNotEmpty) baseUri.resolve('$apiBranch/'), + baseUri, + }; - final document = parser.parse(response.body); final extensions = ['.zip']; - - // Support both full and short hash variants final fullHash = apiCommitHash; final shortHash = apiCommitHash.substring(0, 7); _log.info('Looking for files with hash $fullHash or $shortHash'); - // Look for files with either hash length - final attemptedFiles = []; - for (final element in document.querySelectorAll('a')) { - final href = element.attributes['href']; - if (href != null) attemptedFiles.add(href); - if (href != null && - matchingConfig.matches(href) && - extensions.any(href.endsWith)) { - if (href.contains(fullHash) || href.contains(shortHash)) { - _log.info('Found matching file: $href'); - return '$sourceUrl/$apiBranch/$href'; + for (final listingUrl in candidateListingUrls) { + try { + final response = await http.get(listingUrl); + response.throwIfNotSuccessResponse(); + final document = parser.parse(response.body); + + final attemptedFiles = []; + final resolvedCandidates = + {}; // fileName -> resolvedUrl + for (final element in document.querySelectorAll('a')) { + final href = element.attributes['href']; + if (href == null) continue; + attemptedFiles.add(href); + + // Normalize href for directory indexes that include absolute paths + final hrefPath = Uri.tryParse(href)?.path ?? href; + final fileName = path.basename(hrefPath); + + // Ignore wallet archives on Nebula index + if (fileName.contains('wallet')) { + continue; + } + + final matches = + matchingConfig.matches(fileName) && + extensions.any(hrefPath.endsWith); + if (matches) { + final containsHash = + hrefPath.contains(fullHash) || hrefPath.contains(shortHash); + if (containsHash) { + // Build absolute URL respecting whether href is absolute or relative + final resolvedUrl = href.startsWith('http') + ? href + : listingUrl.resolve(href).toString(); + resolvedCandidates[fileName] = resolvedUrl; + } + } } + + if (resolvedCandidates.isNotEmpty) { + final preferred = matchingConfig.choosePreferred( + resolvedCandidates.keys, + ); + final url = + resolvedCandidates[preferred] ?? resolvedCandidates.values.first; + _log.info('Selected file: $preferred at $listingUrl'); + return url; + } + + _log.fine( + 'No matching files found in $listingUrl. ' + '\nPattern: ${matchingConfig.matchingPattern}, ' + '\nHashes tried: [$fullHash, $shortHash]' + '\nAvailable assets: ${attemptedFiles.join('\n')}', + ); + } catch (e) { + _log.fine('Failed to query listing $listingUrl: $e'); } } - final availableAssets = attemptedFiles.join('\n'); - _log.fine( - 'No matching files found in $sourceUrl. ' - '\nPattern: ${matchingConfig.matchingPattern}, ' - '\nHashes tried: [$fullHash, $shortHash]' - '\nAvailable assets: $availableAssets', + throw Exception( + 'Zip file not found for platform $platform from $sourceUrl', ); - - throw Exception('Zip file not found for platform $platform'); } @override @@ -106,10 +148,12 @@ class DevBuildsArtefactDownloader implements ArtefactDownloader { // Determine the platform to use the appropriate extraction command if (Platform.isMacOS || Platform.isLinux) { // For macOS and Linux, use the `unzip` command with overwrite option - final result = await Process.run( - 'unzip', - ['-o', filePath, '-d', destinationFolder], - ); + final result = await Process.run('unzip', [ + '-o', + filePath, + '-d', + destinationFolder, + ]); if (result.exitCode != 0) { throw Exception('Error extracting zip file: ${result.stderr}'); } diff --git a/packages/komodo_wallet_build_transformer/lib/src/steps/defi_api_build_step/github_artefact_downloader.dart b/packages/komodo_wallet_build_transformer/lib/src/steps/defi_api_build_step/github_artefact_downloader.dart index baf47974..dd7e4063 100644 --- a/packages/komodo_wallet_build_transformer/lib/src/steps/defi_api_build_step/github_artefact_downloader.dart +++ b/packages/komodo_wallet_build_transformer/lib/src/steps/defi_api_build_step/github_artefact_downloader.dart @@ -63,6 +63,7 @@ class GithubArtefactDownloader implements ArtefactDownloader { // If no exact version match found, try matching by commit hash _log.info('Searching for commit hash match'); + final candidates = {}; // fileName -> url for (final release in releases) { for (final asset in release.assets) { final fileName = path.basename(asset.browserDownloadUrl); @@ -73,23 +74,33 @@ class GithubArtefactDownloader implements ArtefactDownloader { branch: release.tagName, ); if (commitHash == apiCommitHash) { - _log.info('Found matching file by commit hash: $fileName'); - return asset.browserDownloadUrl; + candidates[fileName] = asset.browserDownloadUrl; } } } } } + if (candidates.isNotEmpty) { + final preferred = matchingConfig.choosePreferred(candidates.keys); + final url = candidates[preferred] ?? candidates.values.first; + _log.info('Selected file: $preferred'); + return url; + } + // Log available assets to help diagnose issues - final releaseAssets = - releases.expand((r) => r.assets).map((a) => ' - ${a.name}').join('\n'); - _log.fine('No files found matching criteria:\n' - 'Platform: $platform\n' - 'Version: \$version\n' - 'Hash: $fullHash or $shortHash\n' - 'Pattern: ${matchingConfig.matchingPattern}\n' - 'Available assets:\n$releaseAssets'); + final releaseAssets = releases + .expand((r) => r.assets) + .map((a) => ' - ${a.name}') + .join('\n'); + _log.fine( + 'No files found matching criteria:\n' + 'Platform: $platform\n' + 'Version: \$version\n' + 'Hash: $fullHash or $shortHash\n' + 'Pattern: ${matchingConfig.matchingPattern}\n' + 'Available assets:\n$releaseAssets', + ); throw Exception( 'Zip file not found for platform $platform in GitHub releases. ' @@ -135,10 +146,12 @@ class GithubArtefactDownloader implements ArtefactDownloader { // Determine the platform to use the appropriate extraction command if (Platform.isMacOS || Platform.isLinux) { // For macOS and Linux, use the `unzip` command with overwrite option - final result = await Process.run( - 'unzip', - ['-o', filePath, '-d', destinationFolder], - ); + final result = await Process.run('unzip', [ + '-o', + filePath, + '-d', + destinationFolder, + ]); if (result.exitCode != 0) { throw Exception('Error extracting zip file: ${result.stderr}'); } diff --git a/packages/komodo_wallet_build_transformer/lib/src/steps/fetch_defi_api_build_step.dart b/packages/komodo_wallet_build_transformer/lib/src/steps/fetch_defi_api_build_step.dart index e70265be..37ee24c6 100644 --- a/packages/komodo_wallet_build_transformer/lib/src/steps/fetch_defi_api_build_step.dart +++ b/packages/komodo_wallet_build_transformer/lib/src/steps/fetch_defi_api_build_step.dart @@ -72,8 +72,8 @@ class FetchDefiApiStep extends BuildStep { List get platformsToUpdate => selectedPlatform != null && platformsConfig.containsKey(selectedPlatform) - ? [selectedPlatform!] - : platformsConfig.keys.toList(); + ? [selectedPlatform!] + : platformsConfig.keys.toList(); @override Future build() async { @@ -154,24 +154,23 @@ class FetchDefiApiStep extends BuildStep { /// See `BUILD_CONFIG_README.md` in `app_build/BUILD_CONFIG_README.md`. bool? get overrideDefiApiDownload => const bool.hasEnvironment(_overrideEnvName) - ? const bool.fromEnvironment(_overrideEnvName) - : Platform.environment[_overrideEnvName] != null - ? bool.tryParse( - Platform.environment[_overrideEnvName]!, - caseSensitive: false, - ) - : null; + ? const bool.fromEnvironment(_overrideEnvName) + : Platform.environment[_overrideEnvName] != null + ? bool.tryParse( + Platform.environment[_overrideEnvName]!, + caseSensitive: false, + ) + : null; Future _updatePlatform( String platform, ApiBuildPlatformConfig config, ) async { - final updateMessage = - overrideDefiApiDownload != null - ? '${overrideDefiApiDownload! ? 'FORCING' : 'SKIPPING'} update of ' - '$platform platform because OVERRIDE_DEFI_API_DOWNLOAD is set to ' - '$overrideDefiApiDownload' - : null; + final updateMessage = overrideDefiApiDownload != null + ? '${overrideDefiApiDownload! ? 'FORCING' : 'SKIPPING'} update of ' + '$platform platform because OVERRIDE_DEFI_API_DOWNLOAD is set to ' + '$overrideDefiApiDownload' + : null; if (updateMessage != null) { _log.info(updateMessage); @@ -280,13 +279,14 @@ class FetchDefiApiStep extends BuildStep { path.join(destinationFolder, '.api_last_updated_$platform'), ); final currentTimestamp = DateTime.now().toIso8601String(); - final fileChecksum = - sha256.convert(File(zipFilePath).readAsBytesSync()).toString(); + final targetChecksums = List.from( + platformsConfig[platform]!.validZipSha256Checksums, + ); lastUpdatedFile.writeAsStringSync( json.encode({ 'api_commit_hash': apiCommitHash, 'timestamp': currentTimestamp, - 'checksums': [fileChecksum], + 'checksums': targetChecksums, }), ); _log.info('Updated last updated file for $platform.'); @@ -320,8 +320,14 @@ class FetchDefiApiStep extends BuildStep { config.validZipSha256Checksums, ); - if (storedChecksums.toSet().containsAll(targetChecksums)) { - _log.info('version: $apiCommitHash and SHA256 checksum match.'); + // Consider up-to-date only if the stored set exactly matches the target set + final storedSet = storedChecksums.toSet(); + final targetSet = targetChecksums.toSet(); + if (storedSet.length == targetSet.length && + storedSet.containsAll(targetSet)) { + _log.info( + 'version: $apiCommitHash and checksum set matches exactly.', + ); return false; } } @@ -348,7 +354,7 @@ class FetchDefiApiStep extends BuildStep { final npmPath = findNode(); final installResult = await Process.run(npmPath, [ 'install', - ], workingDirectory: artifactOutputPath,); + ], workingDirectory: artifactOutputPath); if (installResult.exitCode != 0) { throw Exception('npm install failed: ${installResult.stderr}'); } @@ -357,7 +363,7 @@ class FetchDefiApiStep extends BuildStep { final buildResult = await Process.run(npmPath, [ 'run', 'build', - ], workingDirectory: artifactOutputPath,); + ], workingDirectory: artifactOutputPath); if (buildResult.exitCode != 0) { throw Exception('npm run build failed: ${buildResult.stderr}'); } diff --git a/packages/komodo_wallet_build_transformer/lib/src/steps/models/api/api_build_platform_config.dart b/packages/komodo_wallet_build_transformer/lib/src/steps/models/api/api_build_platform_config.dart index bd371ed5..bc0a0ee2 100644 --- a/packages/komodo_wallet_build_transformer/lib/src/steps/models/api/api_build_platform_config.dart +++ b/packages/komodo_wallet_build_transformer/lib/src/steps/models/api/api_build_platform_config.dart @@ -16,9 +16,9 @@ class ApiBuildPlatformConfig { required this.validZipSha256Checksums, required this.path, }) : assert( - validZipSha256Checksums.isNotEmpty, - 'At least one valid checksum must be provided', - ); + validZipSha256Checksums.isNotEmpty, + 'At least one valid checksum must be provided', + ); /// Creates a [ApiBuildPlatformConfig] from a JSON map. /// @@ -56,6 +56,9 @@ class ApiBuildPlatformConfig { final matchingConfig = ApiFileMatchingConfig( matchingKeyword: json['matching_keyword'] as String?, matchingPattern: json['matching_pattern'] as String?, + matchingPreference: (json['matching_preference'] is List) + ? List.from(json['matching_preference'] as List) + : const [], ); return ApiBuildPlatformConfig( @@ -81,8 +84,8 @@ class ApiBuildPlatformConfig { /// Converts the configuration to a JSON map. Map toJson() => { - ...matchingConfig.toJson(), - 'valid_zip_sha256_checksums': validZipSha256Checksums, - 'path': path, - }; + ...matchingConfig.toJson(), + 'valid_zip_sha256_checksums': validZipSha256Checksums, + 'path': path, + }; } diff --git a/packages/komodo_wallet_build_transformer/lib/src/steps/models/api/api_file_matching_config.dart b/packages/komodo_wallet_build_transformer/lib/src/steps/models/api/api_file_matching_config.dart index 7c3e0179..cf77d906 100644 --- a/packages/komodo_wallet_build_transformer/lib/src/steps/models/api/api_file_matching_config.dart +++ b/packages/komodo_wallet_build_transformer/lib/src/steps/models/api/api_file_matching_config.dart @@ -1,17 +1,24 @@ -/// Configuration for matching API files using either a simple keyword or regex pattern. +/// Configuration for matching API files using either a simple keyword or regex +/// pattern. class ApiFileMatchingConfig { ApiFileMatchingConfig({ this.matchingKeyword, this.matchingPattern, - }) : assert( - matchingKeyword != null || matchingPattern != null, - 'Either matchingKeyword or matchingPattern must be provided', - ); + List? matchingPreference, + }) : matchingPreference = matchingPreference ?? const [], + assert( + matchingKeyword != null || matchingPattern != null, + 'Either matchingKeyword or matchingPattern must be provided', + ); factory ApiFileMatchingConfig.fromJson(Map json) { + final pref = json['matching_preference']; return ApiFileMatchingConfig( matchingKeyword: json['matching_keyword'] as String?, matchingPattern: json['matching_pattern'] as String?, + matchingPreference: pref is List + ? pref.whereType().toList() + : const [], ); } @@ -21,6 +28,10 @@ class ApiFileMatchingConfig { /// Regular expression pattern to match against the filename final String? matchingPattern; + /// Optional ranking preferences when multiple files match. + /// First substring that matches wins. Earlier items have higher priority. + final List matchingPreference; + /// Checks if the given input string matches either the keyword or pattern bool matches(String input) { if (matchingPattern != null) { @@ -37,8 +48,28 @@ class ApiFileMatchingConfig { return matchingKeyword != null && input.contains(matchingKeyword!); } + /// Given a list of candidate file names, returns the best according to + /// [matchingPreference]. If no preferences are set, returns the first + /// candidate. If no candidate matches any preference, returns the first + /// candidate. + String? choosePreferred(Iterable candidates) { + final list = candidates.toList(); + if (list.isEmpty) return null; + if (matchingPreference.isEmpty) return list.first; + + for (final pref in matchingPreference) { + final found = list.firstWhere((c) => c.contains(pref), orElse: () => ''); + if (found.isNotEmpty) { + return found; + } + } + return list.first; + } + Map toJson() => { - if (matchingKeyword != null) 'matching_keyword': matchingKeyword, - if (matchingPattern != null) 'matching_pattern': matchingPattern, - }; + if (matchingKeyword != null) 'matching_keyword': matchingKeyword, + if (matchingPattern != null) 'matching_pattern': matchingPattern, + if (matchingPreference.isNotEmpty) + 'matching_preference': matchingPreference, + }; } diff --git a/packages/komodo_wallet_cli/bin/update_api_config.dart b/packages/komodo_wallet_cli/bin/update_api_config.dart index 9fbc0544..70bc7c69 100644 --- a/packages/komodo_wallet_cli/bin/update_api_config.dart +++ b/packages/komodo_wallet_cli/bin/update_api_config.dart @@ -33,7 +33,7 @@ void main(List arguments) async { 'branch', abbr: 'b', help: 'Branch to fetch commit from', - defaultsTo: 'master', + defaultsTo: 'main', ) ..addOption( 'repo', @@ -59,6 +59,12 @@ void main(List arguments) async { help: 'Platform to update (e.g., web, macos, windows, linux)', defaultsTo: 'all', ) + ..addOption( + 'commit', + abbr: 'm', + help: + 'Commit hash to pin (short or full). Overrides latest commit lookup.', + ) ..addOption( 'source', abbr: 's', @@ -81,6 +87,13 @@ void main(List arguments) async { abbr: 'v', negatable: false, help: 'Enable verbose logging', + ) + ..addFlag( + 'strict', + negatable: true, + defaultsTo: true, + help: + 'Require exact commit-matching assets for all platforms; fail otherwise. Disable with --no-strict.', ); ArgResults args; @@ -110,9 +123,11 @@ void main(List arguments) async { args['token'] as String? ?? Platform.environment['GITHUB_API_PUBLIC_READONLY_TOKEN']; final platform = args['platform'] as String; + final pinnedCommit = (args['commit'] as String?)?.trim(); final source = args['source'] as String; final mirrorUrl = args['mirror-url'] as String; final verbose = args['verbose'] as bool; + final strict = args['strict'] as bool; try { final fetcher = KdfFetcher( @@ -124,13 +139,33 @@ void main(List arguments) async { source: source, mirrorUrl: mirrorUrl, verbose: verbose, + strict: strict, ); await fetcher.loadBuildConfig(); - log.info('Fetching latest commit for branch: $branch'); - final commitHash = await fetcher.fetchLatestCommit(); - log.info('Latest commit: $commitHash'); + String commitHash; + if (pinnedCommit != null && pinnedCommit.isNotEmpty) { + commitHash = pinnedCommit; + log.info('Using pinned commit: $commitHash'); + } else { + log.info('Fetching latest commit for branch: $branch'); + commitHash = await fetcher.fetchLatestCommit(); + log.info('Latest commit: $commitHash'); + } + + // Ensure the build config is updated with a full 40-char commit SHA + if (commitHash.length < 40) { + try { + final fullSha = await fetcher.resolveCommitSha(commitHash); + log.info('Resolved short commit to full SHA: $fullSha'); + commitHash = fullSha; + } catch (e) { + log.warning( + 'Failed to resolve short commit to full SHA; proceeding with provided value: $commitHash', + ); + } + } if (platform == 'all') { final platforms = fetcher.getSupportedPlatforms(); @@ -189,7 +224,8 @@ Examples: --source mirror \ --config packages/komodo_defi_framework/app_build/build_config.json \ --output-dir packages/komodo_defi_framework/app_build/temp_downloads \ - --verbose + --verbose \ + --strict # Update only the web platform dart run komodo_wallet_cli:update_api_config \ @@ -197,11 +233,12 @@ Examples: --source mirror \ --platform web \ --config packages/komodo_defi_framework/app_build/build_config.json \ - --output-dir packages/komodo_defi_framework/app_build/temp_downloads + --output-dir packages/komodo_defi_framework/app_build/temp_downloads \ + --no-strict # Update using GitHub as the source dart run komodo_wallet_cli:update_api_config \ - --branch master \ + --branch main \ --source github \ --config packages/komodo_defi_framework/app_build/build_config.json \ --output-dir packages/komodo_defi_framework/app_build/temp_downloads @@ -224,6 +261,7 @@ class KdfFetcher { required this.configPath, required this.outputDir, required this.verbose, + this.strict = true, this.token, this.source = 'github', this.mirrorUrl = 'https://sdk.devbuilds.komodo.earth', @@ -256,7 +294,19 @@ class KdfFetcher { late final String owner; late final String repository; final bool verbose; + final bool strict; final log = Logger('KdfFetcher'); + // Preference helper used by URL selectors + String _choosePreferred(Iterable candidates, List prefs) { + final list = candidates.toList(); + if (list.isEmpty) return ''; + if (prefs.isEmpty) return list.first; + for (final pref in prefs) { + final found = list.firstWhere((c) => c.contains(pref), orElse: () => ''); + if (found.isNotEmpty) return found; + } + return list.first; + } Map? _configData; @@ -293,6 +343,25 @@ class KdfFetcher { return data['sha'] as String; } + /// Resolves a short or full commit into a full 40-char SHA via GitHub API + Future resolveCommitSha(String shaOrShort) async { + final url = '$_apiBaseUrl/commits/$shaOrShort'; + log.fine('Resolving commit SHA from: $url'); + + final response = await http.get(Uri.parse(url), headers: _headers); + if (response.statusCode != 200) { + throw Exception( + 'Failed to resolve commit: ${response.statusCode} ${response.reasonPhrase}', + ); + } + final data = jsonDecode(response.body) as Map; + final sha = data['sha'] as String?; + if (sha == null || sha.length != 40) { + throw Exception('Resolved commit SHA is invalid: $sha'); + } + return sha; + } + /// Loads the build config file Future> loadBuildConfig() async { if (_configData != null) { @@ -357,12 +426,15 @@ class KdfFetcher { final checksum = await calculateChecksum(zipFilePath); log.info('Calculated checksum: $checksum'); - // Update platform config with new checksum + // Update platform config with new checksum (accumulate unique) final checksums = - platformConfig['valid_zip_sha256_checksums'] as List; - if (!listEquals(checksums, [checksum])) { + (platformConfig['valid_zip_sha256_checksums'] as List) + .map((e) => e.toString()) + .toSet(); + if (!checksums.contains(checksum)) { + checksums.add(checksum); + platformConfig['valid_zip_sha256_checksums'] = checksums.toList(); log.info('Added new checksum to platform config: $checksum'); - platformConfig['valid_zip_sha256_checksums'] = [checksum]; } else { log.info('Checksum already exists in platform config'); } @@ -380,9 +452,14 @@ class KdfFetcher { (apiConfig['platforms'] as Map)[platform] as Map; - // Get the matching pattern/keyword + // Get the matching pattern/keyword and preference final matchingPattern = platformConfig['matching_pattern'] as String?; final matchingKeyword = platformConfig['matching_keyword'] as String?; + final matchingPreference = (platformConfig['matching_preference'] is List) + ? (platformConfig['matching_preference'] as List) + .whereType() + .toList() + : []; if (matchingPattern == null && matchingKeyword == null) { throw StateError( @@ -396,6 +473,7 @@ class KdfFetcher { commitHash, matchingPattern, matchingKeyword, + matchingPreference, ); } else { return _fetchMirrorDownloadUrl( @@ -403,6 +481,7 @@ class KdfFetcher { commitHash, matchingPattern, matchingKeyword, + matchingPreference, ); } } @@ -413,6 +492,7 @@ class KdfFetcher { String commitHash, String? matchingPattern, String? matchingKeyword, + List matchingPreference, ) async { // Get releases final releasesUrl = '$_apiBaseUrl/releases'; @@ -431,6 +511,7 @@ class KdfFetcher { // Look for the asset with the matching pattern/keyword and commit hash final shortHash = commitHash.substring(0, 7); + final candidates = {}; for (final release in releases) { final assets = release['assets'] as List; @@ -451,37 +532,51 @@ class KdfFetcher { if (matches && (fileName.contains(commitHash) || fileName.contains(shortHash))) { - return asset['browser_download_url'] as String; + candidates[fileName] = asset['browser_download_url'] as String; } } } - // If we couldn't find an exact match, try just matching the platform pattern - for (final release in releases) { - final assets = release['assets'] as List; - - for (final asset in assets) { - final fileName = asset['name'] as String; + if (candidates.isNotEmpty) { + final preferred = _choosePreferred(candidates.keys, matchingPreference); + return candidates[preferred] ?? candidates.values.first; + } - var matches = false; - if (matchingPattern != null) { - try { - final regex = RegExp(matchingPattern); - matches = regex.hasMatch(fileName); - } catch (e) { - log.warning('Invalid regex pattern: $matchingPattern'); + // In strict mode do not fallback – require exact commit match + if (!strict) { + // If we couldn't find an exact match, try just matching the platform pattern + final candidates = {}; + for (final release in releases) { + final assets = release['assets'] as List; + + for (final asset in assets) { + final fileName = asset['name'] as String; + + var matches = false; + if (matchingPattern != null) { + try { + final regex = RegExp(matchingPattern); + matches = regex.hasMatch(fileName); + } catch (e) { + log.warning('Invalid regex pattern: $matchingPattern'); + } + } else if (matchingKeyword != null) { + matches = fileName.contains(matchingKeyword); } - } else if (matchingKeyword != null) { - matches = fileName.contains(matchingKeyword); - } - if (matches) { - log.warning( - 'Could not find exact commit match. Using latest matching asset: $fileName', - ); - return asset['browser_download_url'] as String; + if (matches) { + candidates[fileName] = asset['browser_download_url'] as String; + } } } + if (candidates.isNotEmpty) { + final preferred = _choosePreferred(candidates.keys, matchingPreference); + final url = candidates[preferred] ?? candidates.values.first; + log.warning( + 'Could not find exact commit match. Using latest matching asset: $url', + ); + return url; + } } throw Exception( @@ -495,86 +590,136 @@ class KdfFetcher { String commitHash, String? matchingPattern, String? matchingKeyword, + List matchingPreference, ) async { - final url = '$mirrorUrl/$branch/'; - log.fine('Fetching files from mirror: $url'); - - final response = await http.get(Uri.parse(url)); - if (response.statusCode != 200) { - throw Exception( - 'Failed to fetch files from mirror: ${response.statusCode} ${response.reasonPhrase}', - ); - } + // Try both branch-scoped and base listings; mirrors now expose branch paths + final normalizedMirror = mirrorUrl.endsWith('/') + ? mirrorUrl + : '$mirrorUrl/'; + final mirrorUri = Uri.parse(normalizedMirror); + final listingUrls = { + if (branch.isNotEmpty) mirrorUri.resolve('$branch/'), + mirrorUri, + }; - final document = parser.parse(response.body); final extensions = ['.zip']; - - // Support both full and short hash variants final fullHash = commitHash; final shortHash = commitHash.substring(0, 7); log.info('Looking for files with hash $fullHash or $shortHash'); - // Look for files with either hash length - final attemptedFiles = []; - for (final element in document.querySelectorAll('a')) { - final href = element.attributes['href']; - if (href != null) attemptedFiles.add(href); + for (final baseUrl in listingUrls) { + log.fine('Fetching files from mirror: $baseUrl'); + try { + final response = await http.get(baseUrl); + if (response.statusCode != 200) { + log.fine( + 'Mirror listing failed at $baseUrl: ${response.statusCode} ${response.reasonPhrase}', + ); + continue; + } - if (href != null && extensions.any(href.endsWith)) { - var matches = false; - if (matchingPattern != null) { - try { - final regex = RegExp(matchingPattern); - matches = regex.hasMatch(href); - } catch (e) { - log.warning('Invalid regex pattern: $matchingPattern'); + final document = parser.parse(response.body); + final attemptedFiles = []; + + // First pass: require short/full hash match; collect all candidates + final hashCandidates = {}; + for (final element in document.querySelectorAll('a')) { + final href = element.attributes['href']; + if (href == null) continue; + attemptedFiles.add(href); + + // Prefer checking the path portion for extensions to ignore query params + final hrefPath = Uri.tryParse(href)?.path ?? href; + if (!extensions.any(hrefPath.endsWith)) continue; + if (href.contains('wallet')) continue; // Ignore wallet builds + + var matches = false; + if (matchingPattern != null) { + try { + final regex = RegExp(matchingPattern); + matches = regex.hasMatch(hrefPath); + } catch (e) { + log.warning('Invalid regex pattern: $matchingPattern'); + } + } else if (matchingKeyword != null) { + matches = hrefPath.contains(matchingKeyword); } - } else if (matchingKeyword != null) { - matches = href.contains(matchingKeyword); - } - if (matches && (href.contains(fullHash) || href.contains(shortHash))) { - log.info('Found matching file: $href'); - return '$url$href'; + if (matches && + (hrefPath.contains(fullHash) || hrefPath.contains(shortHash))) { + final fileName = path.basename(hrefPath); + final resolved = href.startsWith('http') + ? href + : baseUrl.resolve(href).toString(); + hashCandidates[fileName] = resolved; + } + } + if (hashCandidates.isNotEmpty) { + final preferred = _choosePreferred( + hashCandidates.keys, + matchingPreference, + ); + final resolved = + hashCandidates[preferred] ?? hashCandidates.values.first; + log.info('Found matching files for commit; selected: $resolved'); + return resolved; } - } - } - - // If we couldn't find an exact match, try just matching the platform pattern - for (final element in document.querySelectorAll('a')) { - final href = element.attributes['href']; - if (href != null && extensions.any(href.endsWith)) { - var matches = false; - if (matchingPattern != null) { - try { - final regex = RegExp(matchingPattern); - matches = regex.hasMatch(href); - } catch (e) { - log.warning('Invalid regex pattern: $matchingPattern'); + // Second pass: latest matching asset without commit constraint (only when not strict) + if (!strict) { + final candidates = {}; + for (final element in document.querySelectorAll('a')) { + final href = element.attributes['href']; + if (href == null) continue; + final hrefPath = Uri.tryParse(href)?.path ?? href; + if (!extensions.any(hrefPath.endsWith)) continue; + if (href.contains('wallet')) continue; + + var matches = false; + if (matchingPattern != null) { + try { + final regex = RegExp(matchingPattern); + matches = regex.hasMatch(hrefPath); + } catch (e) { + log.warning('Invalid regex pattern: $matchingPattern'); + } + } else if (matchingKeyword != null) { + matches = hrefPath.contains(matchingKeyword); + } + + if (matches) { + final fileName = path.basename(hrefPath); + final resolved = href.startsWith('http') + ? href + : baseUrl.resolve(href).toString(); + candidates[fileName] = resolved; + } + } + if (candidates.isNotEmpty) { + final preferred = _choosePreferred( + candidates.keys, + matchingPreference, + ); + final resolved = candidates[preferred] ?? candidates.values.first; + log.warning( + 'Could not find exact commit match. Using latest matching asset: $resolved', + ); + return resolved; } - } else if (matchingKeyword != null) { - matches = href.contains(matchingKeyword); } - if (matches) { - log.warning( - 'Could not find exact commit match. Using latest matching asset: $href', - ); - return '$url$href'; - } + log.fine( + 'No matching files found in $baseUrl. ' + '\nPattern: $matchingPattern, ' + '\nKeyword: $matchingKeyword, ' + '\nHashes tried: [$fullHash, $shortHash]' + '\nAvailable assets: ${attemptedFiles.join('\n')}', + ); + } catch (e) { + log.fine('Error querying mirror listing $baseUrl: $e'); } } - final availableAssets = attemptedFiles.join('\n'); - log.fine( - 'No matching files found in $url. ' - '\nPattern: $matchingPattern, ' - '\nKeyword: $matchingKeyword, ' - '\nHashes tried: [$fullHash, $shortHash]' - '\nAvailable assets: $availableAssets', - ); - throw Exception( 'No matching asset found for platform $platform and commit $commitHash', ); diff --git a/playground/macos/Podfile b/playground/macos/Podfile index c795730d..b52666a1 100644 --- a/playground/macos/Podfile +++ b/playground/macos/Podfile @@ -1,4 +1,4 @@ -platform :osx, '10.14' +platform :osx, '10.15' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/playground/macos/Podfile.lock b/playground/macos/Podfile.lock index 973ac962..673e4d43 100644 --- a/playground/macos/Podfile.lock +++ b/playground/macos/Podfile.lock @@ -2,6 +2,9 @@ PODS: - flutter_inappwebview_macos (0.0.1): - FlutterMacOS - OrderedSet (~> 6.0.3) + - flutter_secure_storage_darwin (10.0.0): + - Flutter + - FlutterMacOS - FlutterMacOS (1.0.0) - komodo_defi_framework (0.0.1): - FlutterMacOS @@ -9,18 +12,15 @@ PODS: - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS - - shared_preferences_foundation (0.0.1): - - Flutter - - FlutterMacOS - url_launcher_macos (0.0.1): - FlutterMacOS DEPENDENCIES: - flutter_inappwebview_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_inappwebview_macos/macos`) + - flutter_secure_storage_darwin (from `Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_darwin/darwin`) - FlutterMacOS (from `Flutter/ephemeral`) - komodo_defi_framework (from `Flutter/ephemeral/.symlinks/plugins/komodo_defi_framework/macos`) - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) - - shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`) - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) SPEC REPOS: @@ -30,26 +30,26 @@ SPEC REPOS: EXTERNAL SOURCES: flutter_inappwebview_macos: :path: Flutter/ephemeral/.symlinks/plugins/flutter_inappwebview_macos/macos + flutter_secure_storage_darwin: + :path: Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_darwin/darwin FlutterMacOS: :path: Flutter/ephemeral komodo_defi_framework: :path: Flutter/ephemeral/.symlinks/plugins/komodo_defi_framework/macos path_provider_foundation: :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin - shared_preferences_foundation: - :path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin url_launcher_macos: :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos SPEC CHECKSUMS: - flutter_inappwebview_macos: bdf207b8f4ebd58e86ae06cd96b147de99a67c9b - FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 - komodo_defi_framework: f716eeef2f8d5cd3a97efe7bb103e8e18285032c + flutter_inappwebview_macos: c2d68649f9f8f1831bfcd98d73fd6256366d9d1d + flutter_secure_storage_darwin: ce237a8775b39723566dc72571190a3769d70468 + FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1 + komodo_defi_framework: 1eb76cee957ff7598498a87bb0d1c470da0f0980 OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94 - path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 - shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 - url_launcher_macos: 5f437abeda8c85500ceb03f5c1938a8c5a705399 + path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 + url_launcher_macos: 0fba8ddabfc33ce0a9afe7c5fef5aab3d8d2d673 -PODFILE CHECKSUM: 236401fc2c932af29a9fcf0e97baeeb2d750d367 +PODFILE CHECKSUM: 9ebaf0ce3d369aaa26a9ea0e159195ed94724cf3 -COCOAPODS: 1.15.2 +COCOAPODS: 1.16.2 diff --git a/playground/macos/Runner.xcodeproj/project.pbxproj b/playground/macos/Runner.xcodeproj/project.pbxproj index 92fdb28c..215af9ea 100644 --- a/playground/macos/Runner.xcodeproj/project.pbxproj +++ b/playground/macos/Runner.xcodeproj/project.pbxproj @@ -556,7 +556,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.14; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -640,7 +640,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.14; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; @@ -690,7 +690,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.14; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; diff --git a/playground/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/playground/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 7dec553d..34196cf6 100644 --- a/playground/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/playground/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -59,6 +59,7 @@ ignoresPersistentStateOnLaunch = "NO" debugDocumentVersioning = "YES" debugServiceExtension = "internal" + enableGPUValidationMode = "1" allowLocationSimulation = "YES">