mirror of
https://github.com/immich-app/immich.git
synced 2026-10-16 21:05:52 +08:00
chore(mobile): rust native core (#31254)
This commit is contained in:
@@ -74,6 +74,7 @@ jobs:
|
||||
- 'mobile/**'
|
||||
- 'open-api/**'
|
||||
- 'i18n/**'
|
||||
- 'native/**'
|
||||
force-filters: |
|
||||
- '.github/workflows/build-mobile.yml'
|
||||
force-events: 'workflow_call,workflow_dispatch'
|
||||
@@ -102,6 +103,20 @@ jobs:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
- name: Restore Rust Cache
|
||||
id: cache-rust-restore
|
||||
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
with:
|
||||
path: |
|
||||
~/.rustup/toolchains
|
||||
~/.rustup/update-hashes
|
||||
~/.cargo/bin
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
key: build-mobile-rust-${{ runner.os }}-${{ hashFiles('native/crates/immich_core_ffi/rust-toolchain.toml', 'native/Cargo.lock') }}
|
||||
restore-keys: |
|
||||
build-mobile-rust-${{ runner.os }}-
|
||||
|
||||
- name: Setup Mise
|
||||
uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1
|
||||
with:
|
||||
@@ -197,6 +212,19 @@ jobs:
|
||||
mobile/android/.gradle
|
||||
key: ${{ steps.cache-gradle-restore.outputs.cache-primary-key }}
|
||||
|
||||
- name: Save Rust Cache
|
||||
id: cache-rust-save
|
||||
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
if: github.ref == 'refs/heads/main'
|
||||
with:
|
||||
path: |
|
||||
~/.rustup/toolchains
|
||||
~/.rustup/update-hashes
|
||||
~/.cargo/bin
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
key: ${{ steps.cache-rust-restore.outputs.cache-primary-key }}
|
||||
|
||||
build-sign-ios:
|
||||
name: Build and sign iOS
|
||||
needs: pre-job
|
||||
@@ -223,6 +251,20 @@ jobs:
|
||||
ref: ${{ inputs.ref || github.sha }}
|
||||
persist-credentials: false
|
||||
|
||||
- name: Restore Rust Cache
|
||||
id: cache-rust-restore
|
||||
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
with:
|
||||
path: |
|
||||
~/.rustup/toolchains
|
||||
~/.rustup/update-hashes
|
||||
~/.cargo/bin
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
key: build-mobile-rust-${{ runner.os }}-${{ hashFiles('native/crates/immich_core_ffi/rust-toolchain.toml', 'native/Cargo.lock') }}
|
||||
restore-keys: |
|
||||
build-mobile-rust-${{ runner.os }}-
|
||||
|
||||
- name: Setup Mise
|
||||
uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1
|
||||
with:
|
||||
@@ -314,3 +356,16 @@ jobs:
|
||||
with:
|
||||
name: ios-release-ipa
|
||||
path: mobile/ios/Runner.ipa
|
||||
|
||||
- name: Save Rust Cache
|
||||
id: cache-rust-save
|
||||
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
if: github.ref == 'refs/heads/main'
|
||||
with:
|
||||
path: |
|
||||
~/.rustup/toolchains
|
||||
~/.rustup/update-hashes
|
||||
~/.cargo/bin
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
key: ${{ steps.cache-rust-restore.outputs.cache-primary-key }}
|
||||
|
||||
@@ -36,6 +36,7 @@ jobs:
|
||||
- 'mobile/**'
|
||||
- 'open-api/**'
|
||||
- 'i18n/en.json'
|
||||
- 'native/**'
|
||||
force-filters: |
|
||||
- '.github/workflows/static_analysis.yml'
|
||||
force-events: 'workflow_dispatch,release'
|
||||
|
||||
@@ -61,6 +61,10 @@ jobs:
|
||||
- 'mobile/**'
|
||||
- 'open-api/**'
|
||||
- 'i18n/**'
|
||||
- 'native/**'
|
||||
- 'mise.toml'
|
||||
native:
|
||||
- 'native/**'
|
||||
- 'mise.toml'
|
||||
machine-learning:
|
||||
- 'machine-learning/**'
|
||||
@@ -71,6 +75,71 @@ jobs:
|
||||
- '.github/workflows/test.yml'
|
||||
force-events: 'workflow_dispatch'
|
||||
|
||||
native-tests:
|
||||
name: Test & Lint Native Core
|
||||
needs: pre-job
|
||||
if: ${{ fromJSON(needs.pre-job.outputs.should_run).native == true }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- id: token
|
||||
uses: immich-app/devtools/actions/create-workflow-token@df46d635b905e618b88fc4c95baa920aeb30b309 # create-workflow-token-action-v3.0.1
|
||||
with:
|
||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
permission-contents: read
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
- name: Restore Rust Cache
|
||||
id: cache-rust-restore
|
||||
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
with:
|
||||
path: |
|
||||
~/.rustup/toolchains
|
||||
~/.rustup/update-hashes
|
||||
~/.cargo/bin
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
native/target
|
||||
key: native-rust-${{ runner.os }}-${{ hashFiles('native/crates/immich_core_ffi/rust-toolchain.toml', 'native/mise.lock', 'native/Cargo.lock') }}
|
||||
restore-keys: |
|
||||
native-rust-${{ runner.os }}-
|
||||
|
||||
- name: Setup Mise
|
||||
uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1
|
||||
with:
|
||||
github_token: ${{ steps.token.outputs.token }}
|
||||
working_directory: ./native
|
||||
|
||||
- name: Check formatting
|
||||
run: mise //native:fmt
|
||||
|
||||
- name: Run clippy
|
||||
run: mise //native:lint
|
||||
|
||||
- name: Run tests
|
||||
run: mise //native:test
|
||||
|
||||
- name: Save Rust Cache
|
||||
id: cache-rust-save
|
||||
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
if: github.ref == 'refs/heads/main'
|
||||
with:
|
||||
path: |
|
||||
~/.rustup/toolchains
|
||||
~/.rustup/update-hashes
|
||||
~/.cargo/bin
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
native/target
|
||||
key: ${{ steps.cache-rust-restore.outputs.cache-primary-key }}
|
||||
|
||||
script-unit-tests:
|
||||
name: Scripts unit tests
|
||||
needs: pre-job
|
||||
@@ -607,6 +676,20 @@ jobs:
|
||||
persist-credentials: false
|
||||
token: ${{ steps.token.outputs.token }}
|
||||
|
||||
- name: Restore Rust Cache
|
||||
id: cache-rust-restore
|
||||
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
with:
|
||||
path: |
|
||||
~/.rustup/toolchains
|
||||
~/.rustup/update-hashes
|
||||
~/.cargo/bin
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
key: mobile-rust-${{ runner.os }}-${{ hashFiles('native/crates/immich_core_ffi/rust-toolchain.toml', 'mobile/mise.lock', 'native/Cargo.lock') }}
|
||||
restore-keys: |
|
||||
mobile-rust-${{ runner.os }}-
|
||||
|
||||
- name: Setup Mise
|
||||
uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1
|
||||
with:
|
||||
@@ -622,6 +705,19 @@ jobs:
|
||||
- name: Run tests
|
||||
run: mise //mobile:test
|
||||
|
||||
- name: Save Rust Cache
|
||||
id: cache-rust-save
|
||||
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
if: github.ref == 'refs/heads/main'
|
||||
with:
|
||||
path: |
|
||||
~/.rustup/toolchains
|
||||
~/.rustup/update-hashes
|
||||
~/.cargo/bin
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
key: ${{ steps.cache-rust-restore.outputs.cache-primary-key }}
|
||||
|
||||
ml-unit-tests:
|
||||
name: Unit Test ML
|
||||
needs: pre-job
|
||||
|
||||
@@ -5,3 +5,4 @@
|
||||
/machine-learning/ @mertalev
|
||||
/e2e/ @danieldietzler
|
||||
/mobile/ @shenlong-tanwen @santoshakil @agg23
|
||||
/native/ @santoshakil
|
||||
|
||||
@@ -104,6 +104,8 @@ To see local changes to `@immich/ui` in Immich, do the following:
|
||||
Always run `mise //mobile:checkout` after switching branches.
|
||||
:::
|
||||
|
||||
The app includes a small Rust core (`native/`) that is compiled from source during the build. With mise the Rust toolchain is installed for you; with your own Flutter install, install [rustup](https://rustup.rs) once and the build fetches the pinned toolchain itself. The first build takes a little longer because of this.
|
||||
|
||||
##### iOS Code Signing
|
||||
|
||||
The Immich Apple Team ID and bundle IDs are specified in `mobile/ios/Signing.xcconfig`. For local development, we provide an override mechanism.
|
||||
|
||||
@@ -12,6 +12,7 @@ config_roots = [
|
||||
"docs",
|
||||
".github",
|
||||
"machine-learning",
|
||||
"native",
|
||||
]
|
||||
|
||||
[tools]
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:immich_native_core/immich_native_core.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
test('native core loads on device', () {
|
||||
expect(version(), isNotEmpty);
|
||||
});
|
||||
}
|
||||
@@ -33,6 +33,13 @@ platform :ios do
|
||||
in_house: false
|
||||
)
|
||||
end
|
||||
|
||||
# Xcode build phases need mise trust saved to disk.
|
||||
def trust_mise_configs
|
||||
return unless system("command -v mise > /dev/null 2>&1")
|
||||
sh("mise trust ../../../mise.toml")
|
||||
sh("mise trust ../../mise.toml")
|
||||
end
|
||||
|
||||
# Helper method to assemble xcargs with optional CUSTOM_GROUP_ID override
|
||||
def build_xcargs(group_id: nil)
|
||||
@@ -102,6 +109,8 @@ end
|
||||
)
|
||||
app_identifier = base_bundle_id
|
||||
|
||||
trust_mise_configs
|
||||
|
||||
# Set version number if provided
|
||||
if version_number
|
||||
increment_version_number(version_number: version_number)
|
||||
@@ -122,6 +131,7 @@ end
|
||||
workspace: "Runner.xcworkspace",
|
||||
configuration: configuration,
|
||||
export_method: "app-store",
|
||||
xcodebuild_formatter: "", # raw xcodebuild output so script-phase errors show in CI logs
|
||||
xcargs: build_xcargs(group_id: group_id),
|
||||
export_options: {
|
||||
provisioningProfiles: {
|
||||
@@ -258,6 +268,8 @@ end
|
||||
|
||||
api_key = get_api_key
|
||||
|
||||
trust_mise_configs
|
||||
|
||||
# Download and install provisioning profiles from App Store Connect
|
||||
# Certificate is imported by GHA workflow into build.keychain
|
||||
sigh(api_key: api_key, app_identifier: DEV_BUNDLE_ID, force: true)
|
||||
@@ -284,6 +296,7 @@ end
|
||||
configuration: "Release",
|
||||
export_method: "app-store",
|
||||
skip_package_ipa: true,
|
||||
xcodebuild_formatter: "", # raw xcodebuild output so script-phase errors show in CI logs
|
||||
xcargs: build_xcargs(group_id: DEV_GROUP_ID),
|
||||
export_options: {
|
||||
provisioningProfiles: {
|
||||
|
||||
@@ -126,3 +126,10 @@ url = "https://download.java.net/java/GA/jdk21.0.2/f2283984656d49d69e91c55847602
|
||||
[[tools."npm:@openapitools/openapi-generator-cli"]]
|
||||
version = "2.40.1"
|
||||
backend = "npm:@openapitools/openapi-generator-cli"
|
||||
|
||||
[[tools.rust]]
|
||||
version = "1.98.0"
|
||||
backend = "core:rust"
|
||||
|
||||
[tools.rust.options]
|
||||
targets = "aarch64-apple-ios,aarch64-apple-ios-sim,aarch64-linux-android,armv7-linux-androideabi,x86_64-apple-ios,x86_64-linux-android"
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
[tools]
|
||||
java = "21.0.2"
|
||||
# RUSTUP_TOOLCHAIN makes rustup ignore the targets in rust-toolchain.toml, so the cross targets are listed here too.
|
||||
rust = { version = "1.98.0", targets = "aarch64-linux-android,armv7-linux-androideabi,x86_64-linux-android,aarch64-apple-ios,aarch64-apple-ios-sim,x86_64-apple-ios" }
|
||||
|
||||
[tools."aqua:flutter/flutter"]
|
||||
version = "3.47.2"
|
||||
@@ -34,6 +36,7 @@ depends = [
|
||||
"codegen:dart",
|
||||
"codegen:drift:migration",
|
||||
"codegen:drift:schema",
|
||||
"codegen:native",
|
||||
"codegen:pigeon",
|
||||
"codegen:translation",
|
||||
]
|
||||
@@ -80,6 +83,15 @@ alias = "watch"
|
||||
description = "Watch and auto-generate dart code"
|
||||
run = "dart run build_runner watch"
|
||||
|
||||
[tasks."codegen:native"]
|
||||
description = "Generate the C header and the ffigen bindings for the native core"
|
||||
dir = "../native/immich_native_core"
|
||||
run = [
|
||||
"cargo build -p immich_core_ffi",
|
||||
"flutter pub get",
|
||||
"dart run ffigen --config ffigen.yaml",
|
||||
]
|
||||
|
||||
[tasks."codegen:pigeon"]
|
||||
alias = "pigeon"
|
||||
description = "Generate pigeon platform code"
|
||||
|
||||
+27
-4
@@ -796,10 +796,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: hooks
|
||||
sha256: "9a62a50b50b769a737bc0a8ff381f333529df3ab746b2f6b02e83760231455ba"
|
||||
sha256: eaac480a35ec0814146c2c48d96aaa829e0e44a7662c88ae84c9edf4bc35651f
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.2"
|
||||
version: "2.2.0"
|
||||
hooks_riverpod:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -928,6 +928,13 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.2"
|
||||
immich_native_core:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "../native/immich_native_core"
|
||||
relative: true
|
||||
source: path
|
||||
version: "0.0.0"
|
||||
immich_ui:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -1140,6 +1147,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.19.1"
|
||||
native_toolchain_rust:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: native_toolchain_rust
|
||||
sha256: "8469a0e32a47a5c84b7fd300de9fd0e8082aac76c37922986324f5082a58350d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.6"
|
||||
native_video_player:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -1458,10 +1473,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: record_use
|
||||
sha256: "2551bd8eecfe95d14ae75f6021ad0248be5c27f138c2ec12fcb52b500b3ba1ed"
|
||||
sha256: "1cb8564af8d43b464294411db9217f5ec04891c6f22ee2c32d73ae05e88a6bd2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.6.0"
|
||||
version: "1.1.1"
|
||||
riverpod:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1772,6 +1787,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.9.4"
|
||||
toml:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: toml
|
||||
sha256: "35a35f782228656a2af31e8c73d1353cc4ef3d683fd68af1111b44631879c05e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.18.0"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -39,6 +39,8 @@ dependencies:
|
||||
hooks_riverpod: ^2.6.1
|
||||
http: ^1.6.0
|
||||
image_picker: ^1.2.3
|
||||
immich_native_core:
|
||||
path: ../native/immich_native_core
|
||||
immich_ui:
|
||||
path: './packages/ui'
|
||||
intl: ^0.20.2
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
/target
|
||||
/crates/immich_core_ffi/include/immich_core.h
|
||||
/immich_native_core/lib/src/bindings.g.dart
|
||||
Generated
+338
@@ -0,0 +1,338 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
|
||||
|
||||
[[package]]
|
||||
name = "cbindgen"
|
||||
version = "0.29.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2ecb53484c9c167ba674026b656d8a27d7657a58e6066aa902bfb1a4aa00ae20"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"indexmap",
|
||||
"log",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"syn 2.0.119",
|
||||
"tempfile",
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223"
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.17.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "immich_core"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "immich_core_ffi"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cbindgen",
|
||||
"immich_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07aa2048142242915a31d35844fb311e0e53fcca590c3a0a40dcf1b841fa09eb"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.189"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.107"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.47"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "6.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "1.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.229"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.229"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.229"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 3.0.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.151"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"serde",
|
||||
"serde_core",
|
||||
"zmij",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.119"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "3.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6275cddf4610d1775e6d1fe9469b2e77d0f39fd98fb7450901b821e0c53649f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.27.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"getrandom",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.9.12+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde_core",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_parser",
|
||||
"toml_writer",
|
||||
"winnow 0.7.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.7.5+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_parser"
|
||||
version = "1.1.3+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56"
|
||||
dependencies = [
|
||||
"winnow 1.0.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_writer"
|
||||
version = "1.1.2+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.61.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.7.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81"
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
|
||||
@@ -0,0 +1,20 @@
|
||||
[workspace]
|
||||
resolver = "3"
|
||||
members = ["crates/immich_core", "crates/immich_core_ffi"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
license = "AGPL-3.0-only"
|
||||
|
||||
[workspace.dependencies]
|
||||
cbindgen = { version = "0.29", default-features = false }
|
||||
|
||||
[workspace.lints.clippy]
|
||||
undocumented_unsafe_blocks = "deny"
|
||||
|
||||
# catch_unwind at the FFI boundary needs unwinding, so no panic = "abort" here.
|
||||
[profile.release]
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
strip = true
|
||||
@@ -0,0 +1,12 @@
|
||||
# Native core
|
||||
|
||||
Rust code that ships inside the mobile app. Flutter compiles it from source through the
|
||||
`immich_native_core` build hook, so building the app needs a Rust toolchain: `mise install`
|
||||
in `mobile/` sets one up, otherwise install [rustup](https://rustup.rs) once and the build
|
||||
fetches the pinned toolchain itself.
|
||||
|
||||
- `crates/immich_core`: the shared logic
|
||||
- `crates/immich_core_ffi`: the C ABI, cbindgen writes `include/immich_core.h`
|
||||
- `immich_native_core`: Flutter package with the build hook and the ffigen bindings
|
||||
|
||||
`mise run build`, `test`, `lint`, and `fmt`. The header and the Dart bindings come from `mise run //mobile:codegen:native`.
|
||||
@@ -0,0 +1,8 @@
|
||||
[package]
|
||||
name = "immich_core"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -0,0 +1,3 @@
|
||||
pub fn core_version() -> &'static str {
|
||||
env!("CARGO_PKG_VERSION")
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
[package]
|
||||
name = "immich_core_ffi"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
# native_toolchain_rust wants the explicit name and both crate types.
|
||||
[lib]
|
||||
name = "immich_core_ffi"
|
||||
crate-type = ["cdylib", "staticlib"]
|
||||
|
||||
[dependencies]
|
||||
immich_core = { path = "../immich_core" }
|
||||
|
||||
[build-dependencies]
|
||||
cbindgen = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -0,0 +1,11 @@
|
||||
use std::path::Path;
|
||||
|
||||
fn main() {
|
||||
// Files that define exported items. A directory here would make Flutter rerun the hook every build.
|
||||
println!("cargo:rerun-if-changed=src/lib.rs");
|
||||
println!("cargo:rerun-if-changed=cbindgen.toml");
|
||||
let crate_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap();
|
||||
cbindgen::generate(&crate_dir)
|
||||
.unwrap_or_else(|e| panic!("cbindgen failed: {e}"))
|
||||
.write_to_file(Path::new(&crate_dir).join("include/immich_core.h"));
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
language = "C"
|
||||
pragma_once = true
|
||||
autogen_warning = "// Generated by cbindgen. Do not edit."
|
||||
@@ -0,0 +1,17 @@
|
||||
# Keep the channel in sync with the rust entries in native/mise.toml and mobile/mise.toml.
|
||||
[toolchain]
|
||||
channel = "1.98.0"
|
||||
targets = [
|
||||
"aarch64-linux-android",
|
||||
"armv7-linux-androideabi",
|
||||
"x86_64-linux-android",
|
||||
"aarch64-apple-ios",
|
||||
"aarch64-apple-ios-sim",
|
||||
"x86_64-apple-ios",
|
||||
"aarch64-apple-darwin",
|
||||
"x86_64-apple-darwin",
|
||||
"aarch64-unknown-linux-gnu",
|
||||
"x86_64-unknown-linux-gnu",
|
||||
"aarch64-pc-windows-msvc",
|
||||
"x86_64-pc-windows-msvc",
|
||||
]
|
||||
@@ -0,0 +1,56 @@
|
||||
use std::ffi::{CString, c_char};
|
||||
use std::panic::{self, UnwindSafe};
|
||||
use std::ptr;
|
||||
|
||||
/// Returns the core version as a C string. Free it with `immich_core_free_string`.
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn immich_core_version() -> *mut c_char {
|
||||
guard(ptr::null_mut(), || {
|
||||
match CString::new(immich_core::core_version()) {
|
||||
Ok(s) => s.into_raw(),
|
||||
Err(_) => ptr::null_mut(),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Releases a string returned by this library. Null is a no-op.
|
||||
///
|
||||
/// # Safety
|
||||
/// `ptr` must come from this library and must not be freed twice.
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn immich_core_free_string(ptr: *mut c_char) {
|
||||
if ptr.is_null() {
|
||||
return;
|
||||
}
|
||||
guard((), || {
|
||||
// SAFETY: the caller guarantees `ptr` came from `CString::into_raw` in this library.
|
||||
drop(unsafe { CString::from_raw(ptr) });
|
||||
});
|
||||
}
|
||||
|
||||
fn guard<T>(fallback: T, f: impl FnOnce() -> T + UnwindSafe) -> T {
|
||||
panic::catch_unwind(f).unwrap_or(fallback)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::ffi::CStr;
|
||||
|
||||
#[test]
|
||||
fn version_roundtrips() {
|
||||
let p = immich_core_version();
|
||||
assert!(!p.is_null());
|
||||
// SAFETY: `p` is a C string from this library.
|
||||
let s = unsafe { CStr::from_ptr(p) }.to_str().unwrap();
|
||||
assert_eq!(s, immich_core::core_version());
|
||||
// SAFETY: `p` is freed exactly once.
|
||||
unsafe { immich_core_free_string(p) };
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn free_null_is_noop() {
|
||||
// SAFETY: null is allowed by the contract.
|
||||
unsafe { immich_core_free_string(ptr::null_mut()) };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
.dart_tool/
|
||||
build/
|
||||
pubspec.lock
|
||||
@@ -0,0 +1,14 @@
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
formatter:
|
||||
page_width: 120
|
||||
|
||||
analyzer:
|
||||
exclude:
|
||||
- build/**
|
||||
- android/**
|
||||
- ios/**
|
||||
- web/**
|
||||
- windows/**
|
||||
- macos/**
|
||||
- linux/**
|
||||
@@ -0,0 +1,9 @@
|
||||
# The asset id must match assetName in hook/build.dart.
|
||||
ffi-native:
|
||||
asset-id: 'package:immich_native_core/src/bindings.g.dart'
|
||||
output: 'lib/src/bindings.g.dart'
|
||||
headers:
|
||||
entry-points:
|
||||
- '../crates/immich_core_ffi/include/immich_core.h'
|
||||
include-directives:
|
||||
- '**/immich_core.h'
|
||||
@@ -0,0 +1,77 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:code_assets/code_assets.dart';
|
||||
import 'package:hooks/hooks.dart';
|
||||
import 'package:native_toolchain_rust/native_toolchain_rust.dart';
|
||||
|
||||
const _crate = '../crates/immich_core_ffi';
|
||||
|
||||
// Cargo's dep-info only lists source files, so these have to rerun the hook too.
|
||||
const _manifests = [
|
||||
'../Cargo.toml',
|
||||
'../Cargo.lock',
|
||||
'../crates/immich_core/Cargo.toml',
|
||||
'$_crate/Cargo.toml',
|
||||
'$_crate/rust-toolchain.toml',
|
||||
];
|
||||
|
||||
void main(List<String> args) async {
|
||||
await build(args, (input, output) async {
|
||||
if (!input.config.buildCodeAssets) return;
|
||||
final code = input.config.code;
|
||||
output.dependencies.addAll(_manifests.map(input.packageRoot.resolve));
|
||||
final target = _crossTarget(code);
|
||||
if (target != null) {
|
||||
await _rustup(['target', 'add', '--toolchain', await _channel(input), target]);
|
||||
}
|
||||
await RustBuilder(
|
||||
assetName: 'src/bindings.g.dart',
|
||||
cratePath: _crate,
|
||||
extraCargoEnvironmentVariables: {if (code.targetOS == OS.android) 'RUSTFLAGS': _androidRustFlags(code)},
|
||||
).run(input: input, output: output);
|
||||
});
|
||||
}
|
||||
|
||||
// mise sets RUSTUP_TOOLCHAIN, which makes rustup ignore rust-toolchain.toml and never install
|
||||
// its targets, so add the one this build needs.
|
||||
String? _crossTarget(CodeConfig code) => switch ((code.targetOS, code.targetArchitecture)) {
|
||||
(OS.android, Architecture.arm) => 'armv7-linux-androideabi',
|
||||
(OS.android, Architecture.arm64) => 'aarch64-linux-android',
|
||||
(OS.android, Architecture.x64) => 'x86_64-linux-android',
|
||||
(OS.iOS, Architecture.arm64) when code.iOS.targetSdk == IOSSdk.iPhoneSimulator => 'aarch64-apple-ios-sim',
|
||||
(OS.iOS, Architecture.arm64) => 'aarch64-apple-ios',
|
||||
(OS.iOS, Architecture.x64) => 'x86_64-apple-ios',
|
||||
_ => null,
|
||||
};
|
||||
|
||||
Future<String> _channel(BuildInput input) async {
|
||||
final toml = await File.fromUri(input.packageRoot.resolve('$_crate/rust-toolchain.toml')).readAsString();
|
||||
final match = RegExp(r'^channel = "([^"]+)"', multiLine: true).firstMatch(toml);
|
||||
if (match == null) throw StateError('channel missing in rust-toolchain.toml');
|
||||
return match.group(1)!;
|
||||
}
|
||||
|
||||
Future<void> _rustup(List<String> args) async {
|
||||
final home = Platform.environment[Platform.isWindows ? 'USERPROFILE' : 'HOME'];
|
||||
ProcessResult result;
|
||||
try {
|
||||
result = await Process.run('rustup', args);
|
||||
} on ProcessException {
|
||||
if (home == null) rethrow;
|
||||
result = await Process.run('$home/.cargo/bin/rustup', args);
|
||||
}
|
||||
if (result.exitCode != 0) {
|
||||
throw ProcessException('rustup', args, '${result.stderr}', result.exitCode);
|
||||
}
|
||||
}
|
||||
|
||||
// Link against minSdk instead of the API 35 that native_toolchain_rust picks.
|
||||
String _androidRustFlags(CodeConfig code) {
|
||||
final triple = switch (code.targetArchitecture) {
|
||||
Architecture.arm => 'armv7a-linux-androideabi',
|
||||
Architecture.arm64 => 'aarch64-linux-android',
|
||||
Architecture.x64 => 'x86_64-linux-android',
|
||||
_ => throw UnsupportedError('Unsupported Android architecture: ${code.targetArchitecture}'),
|
||||
};
|
||||
return '-C link-arg=--target=$triple${code.android.targetNdkApi}';
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import 'package:ffi/ffi.dart';
|
||||
|
||||
import 'src/bindings.g.dart';
|
||||
|
||||
String version() {
|
||||
final ptr = immich_core_version();
|
||||
try {
|
||||
return ptr.cast<Utf8>().toDartString();
|
||||
} finally {
|
||||
immich_core_free_string(ptr);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
name: immich_native_core
|
||||
publish_to: none
|
||||
|
||||
environment:
|
||||
sdk: '>=3.13.0 <4.0.0'
|
||||
|
||||
dependencies:
|
||||
code_assets: ^1.2.1
|
||||
ffi: ^2.2.0
|
||||
flutter:
|
||||
sdk: flutter
|
||||
hooks: ^2.2.0
|
||||
native_toolchain_rust: ^1.0.6
|
||||
|
||||
dev_dependencies:
|
||||
ffigen: ^21.0.0
|
||||
flutter_lints: ^6.0.0
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
@@ -0,0 +1,8 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:immich_native_core/immich_native_core.dart';
|
||||
|
||||
void main() {
|
||||
test('loads the core and returns its version', () {
|
||||
expect(version(), isNotEmpty);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
# @generated - this file is auto-generated by `mise lock` https://mise.en.dev/dev-tools/mise-lock.html
|
||||
|
||||
[[tools.rust]]
|
||||
version = "1.98.0"
|
||||
backend = "core:rust"
|
||||
|
||||
[tools.rust.options]
|
||||
components = "clippy,rustfmt"
|
||||
@@ -0,0 +1,18 @@
|
||||
[tools]
|
||||
rust = { version = "1.98.0", components = "rustfmt,clippy" } # keep in sync with crates/immich_core_ffi/rust-toolchain.toml
|
||||
|
||||
[tasks.build]
|
||||
description = "Build the native crates for the host"
|
||||
run = "cargo build --workspace"
|
||||
|
||||
[tasks.test]
|
||||
description = "Run the Rust tests"
|
||||
run = "cargo test --workspace --locked"
|
||||
|
||||
[tasks.fmt]
|
||||
description = "Check Rust formatting"
|
||||
run = "cargo fmt --all --check"
|
||||
|
||||
[tasks.lint]
|
||||
description = "Run clippy with warnings as errors"
|
||||
run = "cargo clippy --workspace --all-targets --locked -- -D warnings"
|
||||
Reference in New Issue
Block a user