Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integrate uniffi-swift-helper #1

Draft
wants to merge 3 commits into
base: trunk
Choose a base branch
from
Draft
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
6 changes: 4 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,12 @@ native/kotlin/.kotlin/
xcuserdata
DerivedData
fastlane/report.xml
libwordPressFFI.xcframework*
libjetpackFFI.xcframework*
/Package.swift
/Package.resolved

# Auto-generated Swift Files
native/swift/Sources/wordpress-api-wrapper/*.swift
native/swift/Sources/jetpack-api-wrapper/*.swift

# Test Server
.wordpress
Expand Down
15 changes: 15 additions & 0 deletions .swiftlint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
swiftlint_version: 0.57.1
strict: true
included:
- native/swift
excluded: # paths to ignore during linting. Takes precedence over `included`.
- native/swift/Sources/wordpress-api-wrapper/wp_api.swift # auto-generated code
disabled_rules:
# Don't think we should enable this rule.
# See https://github.com/realm/SwiftLint/issues/5263 for context.
- non_optional_string_data_conversion
# Allow using short names (i.e. T, U, ID) for generic types.
- type_name
# The library is still developing. We'll allow todo at this stage.
- todo

17 changes: 17 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ members = [
"jetpack_api",
"jetpack_api_integration_tests",
"jp_uniffi_bindgen",
"swift_helper_cli",
]
resolver = "2"

Expand Down Expand Up @@ -39,3 +40,19 @@ trybuild = "1.0"
uniffi = "0.28"
url = "2.5"
uuid = "1.10"
wp_api = "0.1.0"
wp_api_integration_tests = "0.1.0"
wp_cli = "0.1.0"
wp_contextual = "0.1.0"
wp_derive_request_builder = "0.1.0"
wp_serde_helper = "0.1.0"
uniffi-swift-helper = "0.1.0"

[patch.crates-io]
wp_api = { git = "https://github.com/automattic/wordpress-rs.git", branch = "swift-multiple-crates-in-one-package" }
wp_api_integration_tests = { git = "https://github.com/automattic/wordpress-rs.git", branch = "swift-multiple-crates-in-one-package" }
wp_cli = { git = "https://github.com/automattic/wordpress-rs.git", branch = "swift-multiple-crates-in-one-package" }
wp_contextual = { git = "https://github.com/automattic/wordpress-rs.git", branch = "swift-multiple-crates-in-one-package" }
wp_derive_request_builder = { git = "https://github.com/automattic/wordpress-rs.git", branch = "swift-multiple-crates-in-one-package" }
wp_serde_helper = { git = "https://github.com/automattic/wordpress-rs.git", branch = "swift-multiple-crates-in-one-package" }
uniffi-swift-helper = { git = "https://github.com/automattic/uniffi-swift-helper.git", branch = "real-stuff" }
15 changes: 15 additions & 0 deletions Dockerfile.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
FROM public.ecr.aws/docker/library/swift:6.0

RUN export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true \
&& apt-get update \
&& apt-get install -y \
build-essential \
curl \
make \
libssl-dev

ENV RUSTUP_HOME=/usr/local/rustup \
CARGO_HOME=/usr/local/cargo \
PATH=/usr/local/cargo/bin:$PATH

RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -v -y
93 changes: 20 additions & 73 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,6 @@ ifeq ($(uname), darwin)
dylib_ext := dylib
endif

define MODULEMAP_CONTENT
module libwordpressFFI {
header "libwordpressFFI.h"
export *
}
endef

clean:
@# Help: Remove untracked files from the project via Git.
Expand All @@ -44,16 +38,13 @@ bindings:
mkdir target/swift-bindings
cargo build --release

echo '// Auto-generated' > target/swift-bindings/libwordpressFFI.h

cargo run --release --bin jp_uniffi_bindgen generate --library ./target/release/libwp_api.$(dylib_ext) --out-dir ./target/swift-bindings --language swift
echo '#include "wp_api_uniffi.h"' >> target/swift-bindings/libwordpressFFI.h
echo '// Auto-generated' > target/swift-bindings/libjetpackFFI.h

cargo run --release --bin jp_uniffi_bindgen generate --library ./target/release/libjetpack_api.$(dylib_ext) --out-dir ./target/swift-bindings --language swift
echo '#include "jetpack_api_uniffi.h"' >> target/swift-bindings/libwordpressFFI.h
echo '#include "jetpack_api_uniffi.h"' >> target/swift-bindings/libjetpackFFI.h

echo "$$MODULEMAP_CONTENT" > target/swift-bindings/module.modulemap
cp target/swift-bindings/*.swift native/swift/Sources/wordpress-api-wrapper/
cp target/swift-bindings/jetpack_api.swift native/swift/Sources/jetpack-api-wrapper/

.PHONY: docs # Rebuild docs each time we run this command
docs:
Expand Down Expand Up @@ -93,89 +84,45 @@ release-on-ci:
@echo "Swift package will be released by https://buildkite.com/automattic/wordpress-rs/builds/$$(jq -r '.number' .build/buildkite_release_job_response.json)"
@echo "Once that job finishes, Android libraries will be release by https://buildkite.com/automattic/wordpress-rs/builds?branch=$(WORDPRESS_RS_NEW_VERSION)"

# An XCFramework relies on the .h file and the modulemap to interact with the precompiled binary
xcframework-headers: bindings
rm -rvf target/swift-bindings/headers
mkdir -p target/swift-bindings/headers
cp target/swift-bindings/*.h target/swift-bindings/headers
cp target/swift-bindings/module.modulemap target/swift-bindings/headers/

apple-platform-targets-macos := x86_64-apple-darwin aarch64-apple-darwin
apple-platform-targets-ios := aarch64-apple-ios x86_64-apple-ios aarch64-apple-ios-sim
apple-platform-targets-tvos := aarch64-apple-tvos aarch64-apple-tvos-sim
apple-platform-targets-watchos := arm64_32-apple-watchos x86_64-apple-watchos-sim aarch64-apple-watchos-sim
apple-platform-targets := \
$(apple-platform-targets-macos) \
$(apple-platform-targets-ios) \
$(apple-platform-targets-tvos) \
$(apple-platform-targets-watchos)

ifeq ($(BUILDKITE), true)
CARGO_PROFILE ?= release
else
CARGO_PROFILE ?= dev
endif

cargo_config_library = --config profile.$(CARGO_PROFILE).debug=true --config 'profile.$(CARGO_PROFILE).panic="abort"'

# Set deployment targets for each platform
_build-apple-%-darwin: export MACOSX_DEPLOYMENT_TARGET=$(swift_package_platform_macos)
_build-apple-%-ios _build-apple-%-ios-sim: export IPHONEOS_DEPLOYMENT_TARGET=$(swift_package_platform_ios)
_build-apple-%-tvos _build-apple-%-tvos-sim: export TVOS_DEPLOYMENT_TARGET=$(swift_package_platform_tvos)
_build-apple-%-watchos _build-apple-%-watchos-sim: export WATCHOS_DEPLOYMENT_TARGET=$(swift_package_platform_watchos)

# Use nightly toolchain for tvOS and watchOS
_build-apple-%-tvos _build-apple-%-tvos-sim _build-apple-%-watchos _build-apple-%-watchos-sim: \
CARGO_OPTS = +$(rust_nightly_toolchain) -Z build-std=panic_abort,std

# Build the library for a specific target
_build-apple-%: xcframework-headers
cargo $(CARGO_OPTS) $(cargo_config_library) build --target $* --package wp_api --profile $(CARGO_PROFILE)
cargo $(CARGO_OPTS) $(cargo_config_library) build --target $* --package jetpack_api --profile $(CARGO_PROFILE)

# Build the library for one single platform, including real device and simulator.
build-apple-platform-macos := $(addprefix _build-apple-,$(apple-platform-targets-macos))
build-apple-platform-ios := $(addprefix _build-apple-,$(apple-platform-targets-ios))
build-apple-platform-tvos := $(addprefix _build-apple-,$(apple-platform-targets-tvos))
build-apple-platform-watchos := $(addprefix _build-apple-,$(apple-platform-targets-watchos))

# Creating xcframework for one single platform, including real device and simulator.
xcframework-only-macos: $(build-apple-platform-macos)
xcframework-only-ios: $(build-apple-platform-ios)
xcframework-only-tvos: $(build-apple-platform-tvos)
xcframework-only-watchos: $(build-apple-platform-watchos)
xcframework-only-%:
cargo run --quiet --bin xcframework -- --profile $(CARGO_PROFILE) --targets $(apple-platform-targets-$*)
xcframework-only-macos:
cargo run -q --bin swift_helper_cli build --profile $(CARGO_PROFILE) --only-macos

xcframework-only-ios:
cargo run -q --bin swift_helper_cli build --profile $(CARGO_PROFILE) --only-ios

# Creating xcframework for all platforms.
xcframework-all: $(build-apple-platform-macos) $(build-apple-platform-ios) $(build-apple-platform-tvos) $(build-apple-platform-watchos)
cargo run --quiet --bin xcframework -- --profile $(CARGO_PROFILE) --targets $(apple-platform-targets)
xcframework-all:
cargo run -q --bin swift_helper_cli build --profile $(CARGO_PROFILE)

ifeq ($(SKIP_PACKAGE_WP_API),true)
xcframework:
@echo "Skip building libwordpressFFI.xcframework"
@echo "Skip building libjetpackFFI.xcframework"
else
xcframework: xcframework-all
endif

xcframework-package: xcframework-all
rm -rf libwordpressFFI.xcframework.zip
ditto -c -k --sequesterRsrc --keepParent target/libwordpressFFI.xcframework/ libwordpressFFI.xcframework.zip
rm -rf libjetpackFFI.xcframework.zip
ditto -c -k --sequesterRsrc --keepParent target/libjetpackFFI/libjetpackFFI.xcframework/ libjetpackFFI.xcframework.zip

xcframework-package-checksum:
swift package compute-checksum libwordpressFFI.xcframework.zip | tee libwordpressFFI.xcframework.zip.checksum.txt
swift package compute-checksum libjetpackFFI.xcframework.zip | tee libjetpackFFI.xcframework.zip.checksum.txt

generate-swift-package-manifest:
cargo run -q --bin swift_helper_cli generate-package --project-name jetpack-rs

docker-image-swift:
docker build -t wordpress-rs-swift -f Dockerfile.swift .

swift-linux-library: bindings
rm -rvf target/swift-bindings/libwordpressFFI-linux
mkdir -p target/swift-bindings/libwordpressFFI-linux
cp target/swift-bindings/*.h target/swift-bindings/libwordpressFFI-linux/
cp target/swift-bindings/module.modulemap target/swift-bindings/libwordpressFFI-linux/
cp target/release/libwp_api.a target/swift-bindings/libwordpressFFI-linux/
cp target/release/libjetpack_api.a target/swift-bindings/libwordpressFFI-linux/
swift-linux-library:
cargo run -q --bin swift_helper_cli build --profile $(CARGO_PROFILE)

swift-example-app: swift-example-app-mac swift-example-app-ios

Expand All @@ -191,8 +138,8 @@ test-swift:
test-swift-linux: docker-image-swift
docker run $(docker_opts_shared) -it wordpress-rs-swift make test-swift-linux-in-docker

test-swift-linux-in-docker: swift-linux-library
swift test -Xlinker -Ltarget/swift-bindings/libwordpressFFI-linux -Xlinker -lwp_api -Xlinker -ljetpack_api
test-swift-linux-in-docker: swift-linux-library generate-swift-package-manifest
swift test -Xlinker -Ltarget/libjetpackFFI/linux -Xlinker -ljetpackFFI

test-swift-darwin: xcframework
swift test
Expand Down
8 changes: 4 additions & 4 deletions jetpack_api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ serde = { workspace = true, features = [ "derive" ] }
serde_json = { workspace = true }
thiserror = { workspace = true }
uniffi = { workspace = true }
wp_api = { path = "../../wordpress-rs/wp_api" }
wp_contextual = { path = "../../wordpress-rs/wp_contextual" }
wp_derive_request_builder = { path = "../../wordpress-rs/wp_derive_request_builder", features = [ "generate_request_builder" ] }
wp_serde_helper = { path = "../../wordpress-rs/wp_serde_helper" }
wp_api = { workspace = true }
wp_contextual = { workspace = true }
wp_derive_request_builder = { workspace = true, features = [ "generate_request_builder" ] }
wp_serde_helper = { workspace = true }

[build-dependencies]
uniffi = { workspace = true , features = [ "build", "cli" ] }
26 changes: 21 additions & 5 deletions jetpack_api/src/request.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use std::{fmt::Debug, sync::Arc};

use crate::JetpackRequestExecutionError;
use serde::Deserialize;
use serde::de::DeserializeOwned;
use wp_api::{
request::{WpNetworkRequest, WpNetworkResponse},
request::{ParsedResponse, WpNetworkRequest, WpNetworkResponse},
url_query::FromUrlQueryPairs,
ParsedRequestError,
};

Expand All @@ -18,21 +19,36 @@ pub trait JetpackRequestExecutor: Send + Sync + Debug {
) -> Result<JetpackNetworkResponse, JetpackRequestExecutionError>;
}

#[derive(Debug, Default, uniffi::Object)]
pub struct JetpackDummyObject;

#[uniffi::export]
impl JetpackDummyObject {
#[uniffi::constructor]
pub fn new() -> Self {
Self
}
}

#[derive(Debug, uniffi::Record)]
pub struct JetpackNetworkResponse {
pub inner: WpNetworkResponse,
dummy: Arc<JetpackDummyObject>,
}

impl From<WpNetworkResponse> for JetpackNetworkResponse {
fn from(value: WpNetworkResponse) -> Self {
Self { inner: value }
Self { inner: value, dummy: JetpackDummyObject.into() }
}
}

impl JetpackNetworkResponse {
pub fn parse<'de, T, E>(&'de self) -> Result<T, E>
pub fn parse<ResponseType, DataType, ParamsType, E>(self) -> Result<ResponseType, E>
where
T: Deserialize<'de>,
ResponseType: DeserializeOwned,
ResponseType: From<ParsedResponse<DataType, ParamsType>>,
ParsedResponse<DataType, ParamsType>: From<ResponseType>,
ParamsType: FromUrlQueryPairs,
E: ParsedRequestError,
{
self.inner.parse()
Expand Down
3 changes: 2 additions & 1 deletion jetpack_api/uniffi.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ android_cleaner = false
generate_immutable_records = true

[bindings.swift]
ffi_module_name = "libwordpressFFI"
ffi_module_name = "libjetpackFFI"
ffi_module_filename = "jetpack_api_uniffi"
generate_module_map = false
generate_immutable_records = true
wp_spm_public_module_name = "JetpackAPI"
6 changes: 3 additions & 3 deletions jetpack_api_integration_tests/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ serde = { workspace = true, features = [ "derive" ] }
serde_json = { workspace = true }
tokio = { workspace = true, features = [ "full" ] }
url = { workspace = true }
wp_api = { path = "../../wordpress-rs/wp_api", features = [ "integration-tests" ] }
wp_api_integration_tests = { path = "../../wordpress-rs/wp_api_integration_tests" }
wp_cli = { path = "../../wordpress-rs/wp_cli" }
wp_api = { workspace = true, features = [ "integration-tests" ] }
wp_api_integration_tests = { workspace = true }
wp_cli = { workspace = true }

[dev-dependencies]
paste = { workspace = true }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,5 @@ async fn jetpack_connection() {
Arc::new(AsyncJpNetworking::default()),
);
let connection_status = jetpack_client.connection().status().await.assert_response();
assert!(!connection_status.is_active, "{:#?}", connection_status);
assert!(!connection_status.data.is_active, "{:#?}", connection_status);
}
1 change: 1 addition & 0 deletions native/swift/Sources/jetpack-api/dummy.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
public struct Dummy {};
41 changes: 41 additions & 0 deletions native/swift/Tests/jetpack-api/Tests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import Foundation
import Testing
import WordPressAPIInternal
import JetpackAPIInternal

#if canImport(FoundationNetworking)
import FoundationNetworking
#endif

extension URLSession: JetpackRequestExecutor {
public func execute(request: WpNetworkRequest) async throws -> JetpackAPIInternal.JetpackNetworkResponse {
var urlRequest = URLRequest(url: URL(string: request.url())!)
urlRequest.httpMethod = request.method() == .get ? "GET" : "POST"
urlRequest.httpBody = request.body()?.contents()
for (key, value) in request.headerMap().toMap() {
for v in value {
urlRequest.addValue(v, forHTTPHeaderField: key)
}
}

do {
let (data, response) = try await self.data(for: urlRequest)
let httpResp = response as! HTTPURLResponse
let headers = Dictionary<String, String>(uniqueKeysWithValues: httpResp.allHeaderFields.map { ($0 as! String, $1 as! String) })
let wpResponse = WpNetworkResponse(body: data, statusCode: UInt16(httpResp.statusCode), headerMap: try .fromMap(hashMap: headers))
return .init(inner: wpResponse, dummy: .init())
} catch {
throw JetpackRequestExecutionError.RequestExecutionFailed(statusCode: 500, reason: "Fake")
}
}


}

@Test
func testJetpackAPI() async throws {
let url = try ParsedUrl.parse(input: "https://longreads.com")
let client = UniffiJetpackClient(siteUrl: url, authentication: .none, requestExecutor: URLSession.shared)
let status = try await client.connection().status()
#expect(status.data.isActive)
}
8 changes: 8 additions & 0 deletions swift_helper_cli/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[package]
name = "swift_helper_cli"
version = "0.1.0"
edition = "2021"

[dependencies]
uniffi-swift-helper = { workspace = true }
anyhow = { version = "1.0" }
3 changes: 3 additions & 0 deletions swift_helper_cli/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
fn main() -> anyhow::Result<()> {
uniffi_swift_helper::cli_main()
}