Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/pr_check_macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ jobs:
working-directory: zstandard_macos
run: flutter analyze

- name: Verify version-resolved SwiftPM and Release FFI symbols
run: python3 scripts/test_swiftpm_package.py

- name: Run package tests
working-directory: zstandard_macos
run: flutter test
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/release_workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -669,6 +669,9 @@ jobs:
else
flutter config --no-enable-swift-package-manager
fi
- name: Verify version-resolved SwiftPM and Release FFI symbols
if: matrix.platform == 'macos' && matrix.dependency_manager == 'swiftpm'
run: python3 scripts/test_swiftpm_package.py
- name: Get dependencies and build Apple example
env:
PLATFORM: ${{ matrix.platform }}
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
## 1.5.6 - Release patch fix versioned SwiftPM resolution 188

- Fix SwiftPM resolution of the native package through immutable exact-version dependencies by removing unsafe linker flags (issue #188, PR #189).
- Preserve all ten Dart FFI entry points in iOS/macOS SwiftPM Release builds through plugin registration and a retained native symbol table.
- Add a version-resolved SwiftPM Release regression with dynamic symbol lookup and one-shot/streaming roundtrips to PR and release validation.
- Minimum supported SDKs remain Dart `^3.12.0` and Flutter `>=3.44.0` (published 1.5.0 used Dart `^3.6.0` and Flutter `>=3.3.0`). CI uses Flutter 3.47.2.

## 1.5.5 - Release fix Android native sources from the Windows Pub cache

- Fix Android native-source discovery for consumers using the Windows Pub cache, including paths with spaces, Unicode and correctly resolved package-config URIs.
Expand Down
26 changes: 8 additions & 18 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ let package = Package(
products: [
.library(
name: "zstandard-native",
targets: ["zstandard_native"]
targets: ["zstandard_ffi"]
),
],
targets: [
Expand All @@ -36,24 +36,14 @@ let package = Package(
// internal zstd translation units consume these declarations.
.define("ZSTD_STATIC_LINKING_ONLY"),
.define("ZSTD_DISABLE_ASM"),
],
// Flutter's SwiftPM integration links this target statically into
// the application. Keep the FFI entry points in the final image
// even when release dead-code stripping is enabled.
linkerSettings: [
.unsafeFlags([
"-Xlinker", "-u", "-Xlinker", "_ZSTD_compress",
"-Xlinker", "-u", "-Xlinker", "_ZSTD_decompress",
"-Xlinker", "-u", "-Xlinker", "_ZSTD_compressBound",
"-Xlinker", "-u", "-Xlinker", "_ZSTD_isError",
"-Xlinker", "-u", "-Xlinker", "_ZSTD_getFrameContentSize",
"-Xlinker", "-u", "-Xlinker", "_ZSTD_createDStream",
"-Xlinker", "-u", "-Xlinker", "_ZSTD_initDStream",
"-Xlinker", "-u", "-Xlinker", "_ZSTD_decompressStream",
"-Xlinker", "-u", "-Xlinker", "_ZSTD_freeDStream",
"-Xlinker", "-u", "-Xlinker", "_ZSTD_DStreamOutSize",
])
]
),
// Keep the Dart-only entry points without unsafe dependency flags.
.target(
name: "zstandard_ffi",
dependencies: ["zstandard_native"],
path: "zstandard_native/src/swiftpm",
publicHeadersPath: "include"
),
]
)
5 changes: 3 additions & 2 deletions docs/architecture/ffi-implementation.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,9 @@ application. Repository checkouts may use the adjacent package; published
consumers resolve it through Dart's package configuration. Build scripts never
select an arbitrary matching Pub-cache directory.

SwiftPM force-loads the complete FFI symbol surface because its static linker
could otherwise remove Dart-only entry points. CocoaPods builds the equivalent
SwiftPM plugin registration links a C shim with a retained table of the ten
FFI entry points, preventing its static linker from removing Dart-only symbols
without using unsafe package flags. CocoaPods builds the equivalent
C set in an embedded framework. CLI tests open every required symbol; runtime
roundtrips exercise the same codec.

Expand Down
16 changes: 14 additions & 2 deletions docs/development/apple-dependencies.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ The iOS and macOS manifests depend on the repository-level SwiftPM facade:
```swift
.package(
url: "https://github.com/vypdev/zstandard.git",
exact: "1.5.0"
exact: "1.5.5"
)
```

Expand All @@ -42,7 +42,12 @@ mutable branches are never used in a published manifest.

The SwiftPM target deliberately excludes unsupported or unnecessary upstream
directories and disables assembly for the Apple build. It also preserves the
FFI entry points when release dead-code stripping is enabled. The public
FFI entry points when release dead-code stripping is enabled. The product
includes a project-owned C shim in `zstandard_native/src/swiftpm/`. SwiftPM
plugin registration calls `ZstandardNativeRetainSymbols`, pulling in a
`used` table of the ten Dart FFI entry points. This keeps version-based
resolution compatible with SwiftPM without `unsafeFlags`; the upstream C
sources and CocoaPods implementation remain unchanged. The public
headers in `zstandard_native/src/zstd/include/` are thin forwarding headers;
the actual headers and all C implementation files remain in the canonical
directory.
Expand All @@ -54,6 +59,13 @@ native integration tests, not only by manifest parsing.

## Verification policy

Run `python3 scripts/test_swiftpm_package.py` on macOS for the standalone
regression. It creates an isolated local Git dependency with a semantic version
tag, builds a consumer in Release with dead stripping, resolves every required
symbol using `dlsym`, and exercises one-shot and streaming roundtrips. A local
path or revision dependency alone would not detect the unsafe-flags regression.


Every Apple change must be tested through both dependency managers on the
ARM64 self-hosted macOS runner:

Expand Down
111 changes: 111 additions & 0 deletions scripts/test_swiftpm_package.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
#!/usr/bin/env python3
"""Build a version-resolved SwiftPM consumer and check Release FFI symbols.

The dependency is an isolated, locally tagged copy of the candidate source.
A path or revision dependency would bypass SwiftPM's unsafe-flags check.
"""
from pathlib import Path
import platform
import shutil
import subprocess
import tempfile

ROOT = Path(__file__).resolve().parents[1]

PROBE = r'''#include <dlfcn.h>
#include <stdio.h>
#include <string.h>
#include <zstd.h>
#include <zstandard_ffi.h>

#define REQUIRE(condition) do { \
if (!(condition)) { \
fprintf(stderr, "Failed: %s\n", #condition); \
return 1; \
} \
} while (0)

#define LOAD(name) \
__typeof__(&name) name##Fn = (__typeof__(&name))dlsym(RTLD_DEFAULT, #name); \
REQUIRE(name##Fn != NULL)

int main(void) {
ZstandardNativeRetainSymbols();
LOAD(ZSTD_compress);
LOAD(ZSTD_decompress);
LOAD(ZSTD_compressBound);
LOAD(ZSTD_isError);
LOAD(ZSTD_getFrameContentSize);
LOAD(ZSTD_createDStream);
LOAD(ZSTD_initDStream);
LOAD(ZSTD_decompressStream);
LOAD(ZSTD_freeDStream);
LOAD(ZSTD_DStreamOutSize);

const char input[] = "Version-resolved SwiftPM FFI roundtrip";
char compressed[256];
char output[256];
REQUIRE(ZSTD_compressBoundFn(sizeof(input)) <= sizeof(compressed));
size_t size = ZSTD_compressFn(compressed, sizeof(compressed), input, sizeof(input), 3);
REQUIRE(!ZSTD_isErrorFn(size));
REQUIRE(ZSTD_getFrameContentSizeFn(compressed, size) == sizeof(input));
REQUIRE(ZSTD_decompressFn(output, sizeof(output), compressed, size) == sizeof(input));
REQUIRE(memcmp(input, output, sizeof(input)) == 0);

ZSTD_DStream *stream = ZSTD_createDStreamFn();
REQUIRE(stream != NULL);
REQUIRE(!ZSTD_isErrorFn(ZSTD_initDStreamFn(stream)));
REQUIRE(ZSTD_DStreamOutSizeFn() > 0);
ZSTD_inBuffer source = { compressed, size, 0 };
ZSTD_outBuffer destination = { output, sizeof(output), 0 };
REQUIRE(ZSTD_decompressStreamFn(stream, &destination, &source) == 0);
REQUIRE(source.pos == size && destination.pos == sizeof(input));
REQUIRE(memcmp(input, output, sizeof(input)) == 0);
REQUIRE(!ZSTD_isErrorFn(ZSTD_freeDStreamFn(stream)));
puts("All 10 FFI symbols survive Release dead stripping; one-shot and streaming roundtrips pass.");
return 0;
}
'''


def run(*command, cwd):
subprocess.run(command, cwd=cwd, check=True, timeout=300)


def main():
if platform.system() != 'Darwin':
raise SystemExit('This Apple linker regression test requires macOS and Swift.')
with tempfile.TemporaryDirectory(prefix='zstandard-swiftpm-') as folder:
root = Path(folder)
dependency = root / 'zstandard'
dependency.mkdir()
shutil.copyfile(ROOT / 'Package.swift', dependency / 'Package.swift')
shutil.copytree(ROOT / 'zstandard_native/src', dependency / 'zstandard_native/src')
run('git', 'init', '-q', cwd=dependency)
run('git', 'add', '.', cwd=dependency)
run('git', '-c', 'user.name=SwiftPM regression', '-c',
'user.email=swiftpm-test@example.invalid', 'commit', '-qm', 'Candidate fixture', cwd=dependency)
run('git', 'tag', '1.0.0', cwd=dependency)

consumer = root / 'consumer'
(consumer / 'Sources/Probe').mkdir(parents=True)
(consumer / 'Sources/Probe/main.c').write_text(PROBE)
(consumer / 'Package.swift').write_text('''// swift-tools-version: 5.9
import PackageDescription
let package = Package(
name: "Probe",
platforms: [.macOS(.v10_15)],
dependencies: [.package(url: "''' + dependency.as_uri() + '''", exact: "1.0.0")],
targets: [.executableTarget(name: "Probe", dependencies: [
.product(name: "zstandard-native", package: "zstandard")
])]
)
''')
build = root / 'build'
run('swift', 'build', '--configuration', 'release', '--scratch-path', str(build),
'--cache-path', str(root / 'cache'), '-Xlinker', '-dead_strip', cwd=consumer)
run(str(build / 'release/Probe'), cwd=consumer)


if __name__ == '__main__':
main()
7 changes: 7 additions & 0 deletions zstandard/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
## 1.5.6 - Release patch fix versioned SwiftPM resolution 188

- Fix SwiftPM resolution of the native package through immutable exact-version dependencies by removing unsafe linker flags (issue #188, PR #189).
- Preserve all ten Dart FFI entry points in iOS/macOS SwiftPM Release builds through plugin registration and a retained native symbol table.
- Add a version-resolved SwiftPM Release regression with dynamic symbol lookup and one-shot/streaming roundtrips to PR and release validation.
- Minimum supported SDKs remain Dart `^3.12.0` and Flutter `>=3.44.0` (published 1.5.0 used Dart `^3.6.0` and Flutter `>=3.3.0`). CI uses Flutter 3.47.2.

## 1.5.5 - Release fix Android native sources from the Windows Pub cache

- Fix Android native-source discovery for consumers using the Windows Pub cache, including paths with spaces, Unicode and correctly resolved package-config URIs.
Expand Down
16 changes: 8 additions & 8 deletions zstandard/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: zstandard
description: "Flutter plugin for fast Zstandard compression and decompression across native and WebAssembly platforms."
version: 1.5.5
version: 1.5.6

homepage: https://vyp.dev
repository: https://github.com/vypdev/zstandard
Expand Down Expand Up @@ -28,13 +28,13 @@ flutter:
dependencies:
flutter:
sdk: flutter
zstandard_android: ^1.5.5
zstandard_ios: ^1.5.5
zstandard_linux: ^1.5.5
zstandard_macos: ^1.5.5
zstandard_platform_interface: ^1.5.5
zstandard_web: ^1.5.5
zstandard_windows: ^1.5.5
zstandard_android: ^1.5.6
zstandard_ios: ^1.5.6
zstandard_linux: ^1.5.6
zstandard_macos: ^1.5.6
zstandard_platform_interface: ^1.5.6
zstandard_web: ^1.5.6
zstandard_windows: ^1.5.6

dev_dependencies:
flutter_lints: ^6.0.0
Expand Down
7 changes: 7 additions & 0 deletions zstandard_android/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
## 1.5.6 - Release patch fix versioned SwiftPM resolution 188

- Fix SwiftPM resolution of the native package through immutable exact-version dependencies by removing unsafe linker flags (issue #188, PR #189).
- Preserve all ten Dart FFI entry points in iOS/macOS SwiftPM Release builds through plugin registration and a retained native symbol table.
- Add a version-resolved SwiftPM Release regression with dynamic symbol lookup and one-shot/streaming roundtrips to PR and release validation.
- Minimum supported SDKs remain Dart `^3.12.0` and Flutter `>=3.44.0` (published 1.5.0 used Dart `^3.6.0` and Flutter `>=3.3.0`). CI uses Flutter 3.47.2.

## 1.5.5 - Release fix Android native sources from the Windows Pub cache

- Fix Android native-source discovery for consumers using the Windows Pub cache, including paths with spaces, Unicode and correctly resolved package-config URIs.
Expand Down
6 changes: 3 additions & 3 deletions zstandard_android/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: zstandard_android
description: Android implementation of the Zstandard compression plugin for Flutter applications.
version: 1.5.5
version: 1.5.6

homepage: https://vyp.dev
repository: https://github.com/vypdev/zstandard/tree/master/zstandard_android
Expand All @@ -12,8 +12,8 @@ environment:
dependencies:
flutter:
sdk: flutter
zstandard_platform_interface: ^1.5.5
zstandard_native: ^1.5.5
zstandard_platform_interface: ^1.5.6
zstandard_native: ^1.5.6
ffi: ^2.2.0

dev_dependencies:
Expand Down
7 changes: 7 additions & 0 deletions zstandard_cli/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
## 1.5.6 - Release patch fix versioned SwiftPM resolution 188

- Fix SwiftPM resolution of the native package through immutable exact-version dependencies by removing unsafe linker flags (issue #188, PR #189).
- Preserve all ten Dart FFI entry points in iOS/macOS SwiftPM Release builds through plugin registration and a retained native symbol table.
- Add a version-resolved SwiftPM Release regression with dynamic symbol lookup and one-shot/streaming roundtrips to PR and release validation.
- Minimum supported SDKs remain Dart `^3.12.0` and Flutter `>=3.44.0` (published 1.5.0 used Dart `^3.6.0` and Flutter `>=3.3.0`). CI uses Flutter 3.47.2.

## 1.5.5 - Release fix Android native sources from the Windows Pub cache

- Fix Android native-source discovery for consumers using the Windows Pub cache, including paths with spaces, Unicode and correctly resolved package-config URIs.
Expand Down
Binary file modified zstandard_cli/lib/src/bin/libzstandard_macos.dylib
Binary file not shown.
Binary file modified zstandard_cli/lib/src/bin/zstandard_windows_arm64.dll
Binary file not shown.
Binary file modified zstandard_cli/lib/src/bin/zstandard_windows_x64.dll
Binary file not shown.
2 changes: 1 addition & 1 deletion zstandard_cli/lib/src/cli_runner.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import 'utils/constants.dart';
import 'zstandard_cli_base.dart';

/// Version of the command-line interface.
const String zstandardCliVersion = '1.5.5';
const String zstandardCliVersion = '1.5.6';

/// Runs the compression command and returns a process exit code.
Future<int> runCompressCommand(
Expand Down
4 changes: 2 additions & 2 deletions zstandard_cli/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: zstandard_cli
description: Command-line interface for compressing and decompressing files with Zstandard.
version: 1.5.5
version: 1.5.6

executables:
zstandard-compress: compress
Expand All @@ -16,7 +16,7 @@ dependencies:
ffi: ^2.2.0
path: ^1.9.1
platform: ^3.1.6
zstandard_native: ^1.5.5
zstandard_native: ^1.5.6

dev_dependencies:
coverage: ^1.15.0
Expand Down
7 changes: 7 additions & 0 deletions zstandard_ios/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
## 1.5.6 - Release patch fix versioned SwiftPM resolution 188

- Fix SwiftPM resolution of the native package through immutable exact-version dependencies by removing unsafe linker flags (issue #188, PR #189).
- Preserve all ten Dart FFI entry points in iOS/macOS SwiftPM Release builds through plugin registration and a retained native symbol table.
- Add a version-resolved SwiftPM Release regression with dynamic symbol lookup and one-shot/streaming roundtrips to PR and release validation.
- Minimum supported SDKs remain Dart `^3.12.0` and Flutter `>=3.44.0` (published 1.5.0 used Dart `^3.6.0` and Flutter `>=3.3.0`). CI uses Flutter 3.47.2.

## 1.5.5 - Release fix Android native sources from the Windows Pub cache

- Fix Android native-source discovery for consumers using the Windows Pub cache, including paths with spaces, Unicode and correctly resolved package-config URIs.
Expand Down
2 changes: 1 addition & 1 deletion zstandard_ios/ios/zstandard_ios.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
Pod::Spec.new do |s|
s.cocoapods_version = '>= 1.11.0' # for script_phase :before_headers
s.name = 'zstandard_ios'
s.version = '1.5.5'
s.version = '1.5.6'
s.summary = 'iOS implementation of the Zstandard Flutter plugin.'
s.description = <<-DESC
Native Zstandard compression and bounded decompression for Flutter on iOS.
Expand Down
2 changes: 1 addition & 1 deletion zstandard_ios/ios/zstandard_ios/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ let nativePackageDependency: Package.Dependency = {
// Match the immutable repository tag carrying this plugin release.
return .package(
url: "https://github.com/vypdev/zstandard.git",
exact: "1.5.5"
exact: "1.5.6"
)
}()

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import Flutter
import UIKit
#if SWIFT_PACKAGE
import zstandard_ffi
#endif

public class ZstandardIosPlugin: NSObject, FlutterPlugin {
public static func register(with registrar: FlutterPluginRegistrar) {
#if SWIFT_PACKAGE
ZstandardNativeRetainSymbols()
#endif
let channel = FlutterMethodChannel(name: "plugins.flutter.io/zstandard", binaryMessenger: registrar.messenger())
let instance = ZstandardIosPlugin()
registrar.addMethodCallDelegate(instance, channel: channel)
Expand Down
Loading
Loading