build: unify project development commands

This commit is contained in:
2026-07-21 18:07:06 +02:00
parent 9e8564e013
commit a8a873c83d
22 changed files with 441 additions and 246 deletions

View File

@@ -8,6 +8,9 @@ on:
- "crates/uniffi-bindgen/**" - "crates/uniffi-bindgen/**"
- "Cargo.toml" - "Cargo.toml"
- "Cargo.lock" - "Cargo.lock"
- "Makefile"
- "config.mk"
- "make/**"
- ".github/workflows/apple.yml" - ".github/workflows/apple.yml"
push: push:
branches: branches:
@@ -18,6 +21,9 @@ on:
- "crates/uniffi-bindgen/**" - "crates/uniffi-bindgen/**"
- "Cargo.toml" - "Cargo.toml"
- "Cargo.lock" - "Cargo.lock"
- "Makefile"
- "config.mk"
- "make/**"
- ".github/workflows/apple.yml" - ".github/workflows/apple.yml"
permissions: permissions:
@@ -57,23 +63,5 @@ jobs:
- name: Install XcodeGen - name: Install XcodeGen
run: brew install xcodegen run: brew install xcodegen
- name: Build Rust core (xcframework + Swift bindings) - name: Build and test Apple app
working-directory: apple run: make check-apple
run: ./scripts/build-core.sh debug
- name: Generate Xcode project
working-directory: apple
run: xcodegen generate
- name: Run unit tests (iOS Simulator)
working-directory: apple
run: |
set -euo pipefail
DEVICE=$(xcrun simctl list devices available \
| grep -oE 'iPhone [0-9]+( Pro)?' | head -1)
echo "Testing on: ${DEVICE:-iPhone 16}"
xcodebuild test \
-project VniDrop.xcodeproj \
-scheme VniDrop \
-destination "platform=iOS Simulator,name=${DEVICE:-iPhone 16}" \
CODE_SIGNING_ALLOWED=NO

View File

@@ -4,12 +4,18 @@ on:
pull_request: pull_request:
paths: paths:
- "services/diagnostics-api/**" - "services/diagnostics-api/**"
- "Makefile"
- "config.mk"
- "make/**"
- ".github/workflows/diagnostics-api.yml" - ".github/workflows/diagnostics-api.yml"
push: push:
branches: branches:
- master - master
paths: paths:
- "services/diagnostics-api/**" - "services/diagnostics-api/**"
- "Makefile"
- "config.mk"
- "make/**"
- ".github/workflows/diagnostics-api.yml" - ".github/workflows/diagnostics-api.yml"
permissions: permissions:
@@ -23,9 +29,6 @@ jobs:
quality: quality:
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 15 timeout-minutes: 15
defaults:
run:
working-directory: services/diagnostics-api
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -37,17 +40,5 @@ jobs:
cache: npm cache: npm
cache-dependency-path: services/diagnostics-api/package-lock.json cache-dependency-path: services/diagnostics-api/package-lock.json
- name: Install dependencies - name: Check diagnostics API
run: npm ci run: make check-diagnostics
- name: Verify generated Worker types
run: npm run types:check
- name: Type-check
run: npm run typecheck
- name: Test in the Workers runtime
run: npm test
- name: Validate the deployment bundle
run: npm run deploy:dry-run

View File

@@ -4,12 +4,18 @@ on:
pull_request: pull_request:
paths: paths:
- "docs/**" - "docs/**"
- "Makefile"
- "config.mk"
- "make/**"
- ".github/workflows/docs.yml" - ".github/workflows/docs.yml"
push: push:
branches: branches:
- master - master
paths: paths:
- "docs/**" - "docs/**"
- "Makefile"
- "config.mk"
- "make/**"
- ".github/workflows/docs.yml" - ".github/workflows/docs.yml"
permissions: permissions:
@@ -23,9 +29,6 @@ jobs:
quality: quality:
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 15 timeout-minutes: 15
defaults:
run:
working-directory: docs
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -37,11 +40,5 @@ jobs:
cache: npm cache: npm
cache-dependency-path: docs/package-lock.json cache-dependency-path: docs/package-lock.json
- name: Install dependencies - name: Check documentation website
run: npm ci run: make check-docs
- name: Type-check
run: npm run typecheck
- name: Build
run: npm run build

View File

@@ -17,6 +17,9 @@ on:
- "gradle.properties" - "gradle.properties"
- "gradle/**" - "gradle/**"
- "gradlew" - "gradlew"
- "Makefile"
- "config.mk"
- "make/**"
push: push:
tags: tags:
- "v*.*.*" - "v*.*.*"
@@ -92,37 +95,7 @@ jobs:
echo "app=$version" >> "$GITHUB_OUTPUT" echo "app=$version" >> "$GITHUB_OUTPUT"
- name: Test and build Debian package - name: Test and build Debian package
run: | run: make package-deb VERSION=${{ steps.version.outputs.app }}
./gradlew \
:shared:jvmTest \
:desktopApp:packageReleaseDeb \
-Pvnidrop.version=${{ steps.version.outputs.app }} \
-Pvnidrop.desktop.rustVariant=release \
-Pvnidrop.diagnostics.included=false \
--no-daemon \
--no-configuration-cache \
--stacktrace
- name: Validate Debian package
id: package
env:
VERSION: ${{ steps.version.outputs.app }}
run: |
mapfile -t packages < <(find desktopApp/build/compose/binaries/main-release/deb -maxdepth 1 -type f -name '*.deb')
if (( ${#packages[@]} != 1 )); then
echo "Expected exactly one Debian package, found ${#packages[@]}" >&2
exit 1
fi
output_directory=build/release/linux/deb
output_name="vnidrop_${VERSION}-1_amd64.deb"
mkdir -p "$output_directory"
cp "${packages[0]}" "$output_directory/$output_name"
packaging/linux/verify-package.sh deb "$VERSION" "$output_directory/$output_name"
(
cd "$output_directory"
sha256sum "$output_name" > "$output_name.sha256"
)
- name: Upload Debian artifact - name: Upload Debian artifact
if: github.event_name != 'pull_request' if: github.event_name != 'pull_request'
@@ -225,35 +198,7 @@ jobs:
echo "app=$version" >> "$GITHUB_OUTPUT" echo "app=$version" >> "$GITHUB_OUTPUT"
- name: Build RPM package - name: Build RPM package
run: | run: make package-rpm VERSION=${{ steps.version.outputs.app }}
./gradlew \
:desktopApp:packageReleaseRpm \
-Pvnidrop.version=${{ steps.version.outputs.app }} \
-Pvnidrop.desktop.rustVariant=release \
-Pvnidrop.diagnostics.included=false \
--no-daemon \
--no-configuration-cache \
--stacktrace
- name: Validate RPM package
env:
VERSION: ${{ steps.version.outputs.app }}
run: |
mapfile -t packages < <(find desktopApp/build/compose/binaries/main-release/rpm -maxdepth 1 -type f -name '*.rpm')
if (( ${#packages[@]} != 1 )); then
echo "Expected exactly one RPM package, found ${#packages[@]}" >&2
exit 1
fi
output_directory=build/release/linux/rpm
output_name="vnidrop-${VERSION}-1.x86_64.rpm"
mkdir -p "$output_directory"
cp "${packages[0]}" "$output_directory/$output_name"
packaging/linux/verify-package.sh rpm "$VERSION" "$output_directory/$output_name"
(
cd "$output_directory"
sha256sum "$output_name" > "$output_name.sha256"
)
- name: Upload RPM artifact - name: Upload RPM artifact
if: github.event_name != 'pull_request' if: github.event_name != 'pull_request'

View File

@@ -6,6 +6,9 @@ on:
- "Cargo.toml" - "Cargo.toml"
- "Cargo.lock" - "Cargo.lock"
- "crates/vnidrop/**" - "crates/vnidrop/**"
- "Makefile"
- "config.mk"
- "make/**"
- ".github/workflows/rust-core.yml" - ".github/workflows/rust-core.yml"
push: push:
# Only after merge (or direct master pushes). Feature-branch work is covered # Only after merge (or direct master pushes). Feature-branch work is covered
@@ -16,6 +19,9 @@ on:
- "Cargo.toml" - "Cargo.toml"
- "Cargo.lock" - "Cargo.lock"
- "crates/vnidrop/**" - "crates/vnidrop/**"
- "Makefile"
- "config.mk"
- "make/**"
- ".github/workflows/rust-core.yml" - ".github/workflows/rust-core.yml"
permissions: permissions:
@@ -33,18 +39,10 @@ jobs:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Install Rust quality components - name: Install Rust quality components
run: rustup component add clippy rustfmt run: rustup component add clippy rustfmt
- name: Check formatting - name: Check Rust core
run: cargo fmt --all -- --check run: make check-rust
- name: Run strict Clippy
run: cargo clippy --workspace --all-targets -- -D warnings
- name: Run unit and integration tests
run: cargo test --workspace --all-targets
- name: Check documentation
env:
RUSTDOCFLAGS: -D warnings
run: cargo doc --workspace --no-deps
- name: Install cargo-audit - name: Install cargo-audit
run: cargo install cargo-audit --locked run: cargo install cargo-audit --locked
- name: Audit Rust dependencies - name: Audit Rust dependencies
# Ignores are listed in .cargo/audit.toml for known transitive issues. # Ignores are listed in .cargo/audit.toml for known transitive issues.
run: cargo audit run: make audit-rust

View File

@@ -15,6 +15,9 @@ on:
- "gradle.properties" - "gradle.properties"
- "androidApp/**" - "androidApp/**"
- "desktopApp/**" - "desktopApp/**"
- "Makefile"
- "config.mk"
- "make/**"
- ".github/workflows/shared-kmp.yml" - ".github/workflows/shared-kmp.yml"
push: push:
branches: branches:
@@ -32,6 +35,9 @@ on:
- "gradle.properties" - "gradle.properties"
- "androidApp/**" - "androidApp/**"
- "desktopApp/**" - "desktopApp/**"
- "Makefile"
- "config.mk"
- "make/**"
- ".github/workflows/shared-kmp.yml" - ".github/workflows/shared-kmp.yml"
permissions: permissions:
@@ -94,7 +100,7 @@ jobs:
echo "ANDROID_NDK_ROOT=${{ steps.setup-ndk.outputs.ndk-path }}" >> "$GITHUB_ENV" echo "ANDROID_NDK_ROOT=${{ steps.setup-ndk.outputs.ndk-path }}" >> "$GITHUB_ENV"
- name: Run shared JVM tests - name: Run shared JVM tests
run: ./gradlew :shared:jvmTest --no-daemon --stacktrace run: make check-shared
- name: Verify Android native libraries - name: Verify Android native libraries
run: ./gradlew :androidApp:verifyDebugVnidropLibraries --no-daemon --stacktrace run: make verify-android-libs

1
.gitignore vendored
View File

@@ -19,6 +19,7 @@ captures
node_modules/ node_modules/
target/ target/
.junie .junie
config.override.mk
# Local design export scratch # Local design export scratch
output/ output/

View File

@@ -53,7 +53,7 @@ Domain docs (reference, do not paste into PRs):
## Build and test ## Build and test
Install prerequisites when missing: Rust stable + rustfmt + clippy, JDK 17, Install prerequisites when missing: GNU Make + Bash, Rust stable + rustfmt + clippy, JDK 17,
Android NDK/SDK only if building Android, Xcode only for the native Apple app. Android NDK/SDK only if building Android, Xcode only for the native Apple app.
### Rust core (`crates/vnidrop` or workspace root) ### Rust core (`crates/vnidrop` or workspace root)
@@ -61,43 +61,40 @@ Android NDK/SDK only if building Android, Xcode only for the native Apple app.
Run from the **repo root** (Cargo workspace): Run from the **repo root** (Cargo workspace):
```bash ```bash
cargo fmt --all -- --check make check-rust
cargo clippy --workspace --all-targets -- -D warnings
cargo test --workspace --all-targets
``` ```
Focused: Focused:
```bash ```bash
cargo test -p vnidrop make test-rust
cargo test -p vnidrop --test output_sink make test-rust-output-sink
cargo test -p vnidrop --test transfer make test-rust-transfer
cargo test -p vnidrop --test approval make test-rust-approval
cargo test -p vnidrop --test lifecycle make test-rust-lifecycle
``` ```
After finishing Rust edits, format: After finishing Rust edits, format:
```bash ```bash
cargo fmt --all make format
``` ```
CI also runs `cargo doc --workspace --no-deps` with `RUSTDOCFLAGS=-D warnings` `make check-rust` includes documentation with warnings denied, matching
(see `.github/workflows/rust-core.yml`). Run it before large Rust public-API changes. `.github/workflows/rust-core.yml`.
### Shared KMP / Compose (`shared/`) ### Shared KMP / Compose (`shared/`)
```bash ```bash
./gradlew :shared:jvmTest make check-shared
./gradlew :shared:compileKotlinJvm
``` ```
Other targets (slower / machine-dependent): Other targets (slower / machine-dependent):
```bash ```bash
./gradlew :shared:testAndroidHostTest make test-android-host
./gradlew :androidApp:assembleDebug make check-android
./gradlew :desktopApp:run make run-desktop
``` ```
**Note:** `jvmTest` CI runs on **Linux**. Gobley host cargo is enabled for the **Note:** `jvmTest` CI runs on **Linux**. Gobley host cargo is enabled for the
@@ -108,10 +105,10 @@ Rust library.
| You changed… | Minimum verification | | You changed… | Minimum verification |
|--------------|----------------------| |--------------|----------------------|
| `crates/vnidrop/**` only | `cargo fmt`, `cargo clippy … -D warnings`, `cargo test -p vnidrop` | | `crates/vnidrop/**` only | `make check-rust` |
| Cancel / export / sinks | Above + `cargo test -p vnidrop --test output_sink` | | Cancel / export / sinks | Above + `make test-rust-output-sink` |
| `shared/**` only | `./gradlew :shared:jvmTest` | | `shared/**` only | `make test-shared` |
| Both | Rust suite + `:shared:jvmTest` | | Both | `make test-rust test-shared` |
| Docs only | No suite required; verify links/paths | | Docs only | No suite required; verify links/paths |
Do not kill long `cargo` / Gradle runs mid-flight unless they hang past several Do not kill long `cargo` / Gradle runs mid-flight unless they hang past several

View File

@@ -35,6 +35,7 @@ Use a branch name that describes the outcome, such as
Install the tools needed for the area you plan to change: Install the tools needed for the area you plan to change:
- GNU Make and Bash for the root command interface
- JDK 17 or newer for Gradle and application builds - JDK 17 or newer for Gradle and application builds
- Rust stable with `rustfmt` and Clippy for the transfer core - Rust stable with `rustfmt` and Clippy for the transfer core
- Android SDK and NDK for Android builds - Android SDK and NDK for Android builds
@@ -44,6 +45,25 @@ Install the tools needed for the area you plan to change:
The first Rust and Gradle builds may take several minutes while dependencies are The first Rust and Gradle builds may take several minutes while dependencies are
downloaded and native components are compiled. downloaded and native components are compiled.
## Command Interface
Run development commands through the root `Makefile`. It keeps local and CI
commands aligned while continuing to delegate builds to Cargo, Gradle, Xcode,
Bun, and npm:
```bash
make help # list commands
make doctor # report missing host tools
make setup # install repository-local JavaScript dependencies
make check # portable Rust, shared, localization, docs, and service checks
```
Configuration can be passed on the command line, for example
`make package-deb VERSION=1.2.0`, or placed in an ignored
`config.override.mk`. Windows use requires GNU Make in a Bash environment; the
underlying Gradle and PowerShell entry points remain available when Make is not
installed.
## Repository Structure ## Repository Structure
| Path | Purpose | | Path | Purpose |
@@ -87,55 +107,48 @@ Run checks from the repository root. Choose the suite for the files you changed.
### Rust Core ### Rust Core
```bash ```bash
cargo fmt --all make format
cargo clippy --workspace --all-targets -- -D warnings make test-rust
cargo test -p vnidrop
``` ```
For cancel, export, or output-sink changes, also run: For cancel, export, or output-sink changes, also run:
```bash ```bash
cargo test -p vnidrop --test output_sink make test-rust-output-sink
``` ```
For broader core changes, run the complete workspace suite: For broader core changes, run the complete workspace suite:
```bash ```bash
cargo test --workspace --all-targets make check-rust
``` ```
### Shared Kotlin and Compose ### Shared Kotlin and Compose
```bash ```bash
./gradlew :shared:jvmTest make test-shared
``` ```
Platform-specific checks may also be appropriate: Platform-specific checks may also be appropriate:
```bash ```bash
./gradlew :shared:testAndroidHostTest make test-android-host
./gradlew :androidApp:assembleDebug make check-android
``` ```
### Native Apple App ### Native Apple App
```bash ```bash
cd apple make check-apple
./scripts/build-core.sh debug
xcodegen generate
xcodebuild test \
-project VniDrop.xcodeproj \
-scheme VniDrop \
-destination 'platform=iOS Simulator,name=iPhone 16' \
CODE_SIGNING_ALLOWED=NO
``` ```
Override the selected simulator when needed with
`make check-apple APPLE_DESTINATION='platform=iOS Simulator,name=iPhone 16'`.
### Diagnostics Service ### Diagnostics Service
```bash ```bash
cd services/diagnostics-api make check-diagnostics
npm ci
npm run check
``` ```
If a required check cannot run on your machine, explain why in the pull request If a required check cannot run on your machine, explain why in the pull request

181
Makefile Normal file
View File

@@ -0,0 +1,181 @@
ROOT := $(patsubst %/,%,$(dir $(abspath $(lastword $(MAKEFILE_LIST)))))
SHELL := bash
.SHELLFLAGS := -eu -o pipefail -c
.DEFAULT_GOAL := help
include $(ROOT)/config.mk
-include $(ROOT)/config.override.mk
include $(ROOT)/make/release.mk
.PHONY: help doctor setup setup-localization setup-docs setup-diagnostics
.PHONY: format test check check-rust audit-rust test-rust test-rust-all
.PHONY: test-rust-transfer test-rust-approval test-rust-lifecycle test-rust-output-sink
.PHONY: check-shared test-shared test-android-host check-android verify-android-libs build-android run-desktop
.PHONY: apple-core apple-project open-apple-project open-apple build-apple-macos build-apple-ios check-apple
.PHONY: check-localization localization localization-migrate
.PHONY: check-docs run-docs check-diagnostics run-diagnostics diagnostics-db-local diagnostics-db-remote diagnostics-typegen deploy-diagnostics
help: ## Show available commands and common configuration variables.
@grep -hE '^[A-Za-z0-9_.-]+:.*## ' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*## "} {printf " %-28s %s\n", $$1, $$2}'
@printf '\nCommon variables:\n'
@printf ' %-28s %s\n' 'VERSION=x.y.z' 'Package version (default: $(VERSION))'
@printf ' %-28s %s\n' 'APPLE_PROFILE=debug|release' 'Rust profile for the Apple XCFramework'
@printf ' %-28s %s\n' 'APPLE_CONFIGURATION=...' 'Xcode configuration (default: $(APPLE_CONFIGURATION))'
@printf ' %-28s %s\n' 'APPLE_DESTINATION=...' 'Optional xcodebuild destination override'
@printf ' %-28s %s\n' 'APPLE_CODE_SIGNING=NO|YES' 'Enable Apple code signing (default: $(APPLE_CODE_SIGNING))'
doctor: ## Check that tools required by the current host are available.
@missing=0; \
for tool in "$(firstword $(CARGO))" java "$(firstword $(NPM))" "$(firstword $(BUN))"; do \
if command -v "$$tool" >/dev/null 2>&1; then \
printf 'ok %s\n' "$$tool"; \
else \
printf 'missing %s\n' "$$tool"; \
missing=1; \
fi; \
done; \
if [[ ! -f "$(GRADLE)" ]]; then printf 'missing %s\n' "$(GRADLE)"; missing=1; else printf 'ok %s\n' "$(GRADLE)"; fi; \
if [[ "$(HOST_OS)" == macos ]]; then \
for tool in "$(firstword $(XCODEBUILD))" "$(firstword $(XCODEGEN))"; do \
if command -v "$$tool" >/dev/null 2>&1; then printf 'ok %s\n' "$$tool"; else printf 'missing %s\n' "$$tool"; missing=1; fi; \
done; \
fi; \
exit $$missing
setup: setup-localization setup-docs setup-diagnostics ## Install repository-local JavaScript dependencies.
setup-localization: ## Install localization CLI dependencies with Bun.
cd $(ROOT)/localization && $(BUN) install --frozen-lockfile
setup-docs: ## Install documentation website dependencies.
cd $(ROOT)/docs && $(NPM) ci
setup-diagnostics: ## Install diagnostics Worker dependencies.
cd $(ROOT)/services/diagnostics-api && $(NPM) ci
format: ## Format Rust sources.
cd $(ROOT) && $(CARGO) fmt --all
test: test-rust test-shared ## Run the main Rust and shared JVM test suites.
check: check-rust check-shared check-localization check-docs check-diagnostics ## Run portable pre-PR verification.
check-rust: ## Run Rust formatting, lint, tests, and documentation checks.
cd $(ROOT) && $(CARGO) fmt --all -- --check
cd $(ROOT) && $(CARGO) clippy --workspace --all-targets -- -D warnings
cd $(ROOT) && $(CARGO) test --workspace --all-targets
cd $(ROOT) && RUSTDOCFLAGS='-D warnings' $(CARGO) doc --workspace --no-deps
audit-rust: ## Audit Rust dependencies (requires cargo-audit).
cd $(ROOT) && $(CARGO) audit
test-rust: ## Run the focused Rust core suite.
cd $(ROOT) && $(CARGO) test -p vnidrop
test-rust-all: ## Run every Rust workspace test target.
cd $(ROOT) && $(CARGO) test --workspace --all-targets
test-rust-transfer: ## Run Rust transfer integration tests.
cd $(ROOT) && $(CARGO) test -p vnidrop --test transfer
test-rust-approval: ## Run Rust approval integration tests.
cd $(ROOT) && $(CARGO) test -p vnidrop --test approval
test-rust-lifecycle: ## Run Rust lifecycle integration tests.
cd $(ROOT) && $(CARGO) test -p vnidrop --test lifecycle
test-rust-output-sink: ## Run Rust output-sink integration tests.
cd $(ROOT) && $(CARGO) test -p vnidrop --test output_sink
check-shared: ## Test and compile the shared Android/JVM module.
cd $(ROOT) && $(GRADLE) :shared:jvmTest :shared:compileKotlinJvm $(GRADLE_FLAGS)
test-shared: ## Run shared JVM tests.
cd $(ROOT) && $(GRADLE) :shared:jvmTest $(GRADLE_FLAGS)
test-android-host: ## Run Android host-side shared tests.
cd $(ROOT) && $(GRADLE) :shared:testAndroidHostTest $(GRADLE_FLAGS)
check-android: ## Build Android debug and verify packaged Rust libraries.
cd $(ROOT) && $(GRADLE) :androidApp:assembleDebug :androidApp:verifyDebugVnidropLibraries $(GRADLE_FLAGS)
verify-android-libs: ## Verify the Rust libraries packaged in the Android debug app.
cd $(ROOT) && $(GRADLE) :androidApp:verifyDebugVnidropLibraries $(GRADLE_FLAGS)
build-android: ## Build the Android debug APK.
cd $(ROOT) && $(GRADLE) :androidApp:assembleDebug $(GRADLE_FLAGS)
run-desktop: ## Run the Windows/Linux Compose desktop app.
cd $(ROOT) && $(GRADLE) :desktopApp:run $(GRADLE_FLAGS)
apple-core: ## Build the Rust XCFramework and generated Swift bindings.
@test "$(HOST_OS)" = macos || { printf 'Apple builds require macOS.\n' >&2; exit 1; }
cd $(ROOT) && apple/scripts/build-core.sh $(APPLE_PROFILE)
apple-project: apple-core ## Generate the native Apple Xcode project.
cd $(ROOT)/apple && $(XCODEGEN) generate
open-apple-project: apple-project ## Generate and open the native Apple Xcode project.
cd $(ROOT)/apple && $(OPEN) VniDrop.xcodeproj
build-apple-macos: apple-project ## Build the native macOS app (unsigned by default).
cd $(ROOT)/apple && $(XCODEBUILD) -project VniDrop.xcodeproj -scheme VniDrop -configuration $(APPLE_CONFIGURATION) -derivedDataPath "$(APPLE_DERIVED_DATA)" -destination 'platform=macOS' CODE_SIGNING_ALLOWED=$(APPLE_CODE_SIGNING) CODE_SIGNING_REQUIRED=$(APPLE_CODE_SIGNING) build
open-apple: build-apple-macos ## Build and launch the native macOS app.
@test -d "$(APPLE_DERIVED_DATA)/Build/Products/$(APPLE_CONFIGURATION)/VniDrop.app" || { printf 'Built macOS app was not found.\n' >&2; exit 1; }
$(OPEN) "$(APPLE_DERIVED_DATA)/Build/Products/$(APPLE_CONFIGURATION)/VniDrop.app"
build-apple-ios: apple-project ## Build the native iOS simulator app (unsigned by default).
@destination="$(APPLE_DESTINATION)"; \
if [[ -z "$$destination" ]]; then \
device_id="$$(xcrun simctl list devices available | sed -nE '/iPhone/ s/.*\(([0-9A-F-]{36})\) \((Booted|Shutdown)\).*/\1/p' | head -1 || true)"; \
[[ -n "$$device_id" ]] || { printf 'No available iPhone simulator found. Set APPLE_DESTINATION explicitly.\n' >&2; exit 1; }; \
destination="platform=iOS Simulator,id=$$device_id"; \
fi; \
cd $(ROOT)/apple && $(XCODEBUILD) -project VniDrop.xcodeproj -scheme VniDrop -configuration $(APPLE_CONFIGURATION) -derivedDataPath "$(APPLE_DERIVED_DATA)" -destination "$$destination" CODE_SIGNING_ALLOWED=$(APPLE_CODE_SIGNING) CODE_SIGNING_REQUIRED=$(APPLE_CODE_SIGNING) build
check-apple: apple-project ## Build the Apple core and run iOS simulator tests.
@destination="$(APPLE_DESTINATION)"; \
if [[ -z "$$destination" ]]; then \
device_id="$$(xcrun simctl list devices available | sed -nE '/iPhone/ s/.*\(([0-9A-F-]{36})\) \((Booted|Shutdown)\).*/\1/p' | head -1 || true)"; \
[[ -n "$$device_id" ]] || { printf 'No available iPhone simulator found. Set APPLE_DESTINATION explicitly.\n' >&2; exit 1; }; \
destination="platform=iOS Simulator,id=$$device_id"; \
fi; \
printf 'Testing on: %s\n' "$$destination"; \
cd $(ROOT)/apple && $(XCODEBUILD) test -project VniDrop.xcodeproj -scheme VniDrop -configuration $(APPLE_CONFIGURATION) -derivedDataPath "$(APPLE_DERIVED_DATA)" -destination "$$destination" CODE_SIGNING_ALLOWED=$(APPLE_CODE_SIGNING) CODE_SIGNING_REQUIRED=$(APPLE_CODE_SIGNING)
check-localization: setup-localization ## Validate the localization source catalog.
cd $(ROOT)/localization && $(BUN) run validate
localization: setup-localization ## Regenerate Apple and KMP localization resources.
cd $(ROOT)/localization && $(BUN) run generate
localization-migrate: setup-localization ## Rebuild strings.json from platform resources.
cd $(ROOT)/localization && $(BUN) run migrate
check-docs: setup-docs ## Lint, type-check, and build the documentation website.
cd $(ROOT)/docs && $(NPM) run lint
cd $(ROOT)/docs && $(NPM) run typecheck
cd $(ROOT)/docs && $(NPM) run build
run-docs: setup-docs ## Run the documentation development server.
cd $(ROOT)/docs && $(NPM) run dev
check-diagnostics: setup-diagnostics ## Run diagnostics types, tests, and deployment dry-run.
cd $(ROOT)/services/diagnostics-api && $(NPM) run check
run-diagnostics: setup-diagnostics ## Run the diagnostics Worker locally.
cd $(ROOT)/services/diagnostics-api && $(NPM) run dev
diagnostics-db-local: setup-diagnostics ## Apply diagnostics database migrations locally.
cd $(ROOT)/services/diagnostics-api && $(NPM) run db:migrate:local
diagnostics-db-remote: setup-diagnostics ## Apply diagnostics database migrations to the configured remote D1 database.
cd $(ROOT)/services/diagnostics-api && $(NPM) run db:migrate:remote
diagnostics-typegen: setup-diagnostics ## Regenerate diagnostics Worker binding types.
cd $(ROOT)/services/diagnostics-api && $(NPM) run typegen
deploy-diagnostics: setup-diagnostics ## Check and deploy the diagnostics Worker to Cloudflare.
cd $(ROOT)/services/diagnostics-api && $(NPM) run deploy

View File

@@ -118,17 +118,21 @@ if you want to try the current version.
git clone https://github.com/vnidrop/vnidrop.git git clone https://github.com/vnidrop/vnidrop.git
cd vnidrop cd vnidrop
# List the supported development commands and check prerequisites
make help
make doctor
# Windows/Linux desktop # Windows/Linux desktop
./gradlew :desktopApp:run make run-desktop
# Android debug build # Android debug build
./gradlew :androidApp:assembleDebug make build-android
# iOS, iPadOS, and macOS # Build and launch the macOS app
cd apple make open-apple
./scripts/build-core.sh debug
xcodegen generate # Open the native project for iOS, iPadOS, or Xcode development
open VniDrop.xcodeproj make open-apple-project
``` ```
See [`CONTRIBUTING.md`](CONTRIBUTING.md) for prerequisites, development setup, See [`CONTRIBUTING.md`](CONTRIBUTING.md) for prerequisites, development setup,

View File

@@ -30,21 +30,24 @@ Prerequisites: Xcode, Rust with the Apple targets
`aarch64-apple-darwin`), and `xcodegen` (`brew install xcodegen`). `aarch64-apple-darwin`), and `xcodegen` (`brew install xcodegen`).
```bash ```bash
# 1. Build the Rust core and generate the Swift bindings + xcframework. # From the repository root:
apple/scripts/build-core.sh debug # or: release (see note below) make apple-core # Rust core, Swift bindings, and XCFramework
make apple-project # generate apple/VniDrop.xcodeproj
# 2. Generate the Xcode project. make open-apple-project # generate and open the project in Xcode
cd apple && xcodegen generate make build-apple-macos # unsigned macOS build
make open-apple # build and launch the macOS app
# 3. Open and run, or build from the CLI: make build-apple-ios # unsigned iOS simulator build
open VniDrop.xcodeproj make check-apple # iOS simulator tests
# macOS:
xcodebuild -project VniDrop.xcodeproj -scheme VniDrop -destination 'platform=macOS' build
# iOS simulator:
xcodebuild -project VniDrop.xcodeproj -scheme VniDrop \
-destination 'platform=iOS Simulator,name=iPhone 15' build
``` ```
Use `APPLE_PROFILE=release` to request a release Rust core, or set
`APPLE_DESTINATION` to override the automatically selected iOS simulator.
Code signing is disabled for the app and test targets; local and CI builds do
not require an Apple Development team or provisioning profile. Make builds can
opt in with `APPLE_CODE_SIGNING=YES`. For signed builds from Xcode, create the
ignored `apple/Local.xcconfig` and override the signing settings there, including
the development team.
## Command-line typecheck & tests ## Command-line typecheck & tests
`Package.swift` builds the same sources as a library (minus the `@main` entry), `Package.swift` builds the same sources as a library (minus the `@main` entry),

View File

@@ -1,9 +1,6 @@
// Committed signing config. Contains no secrets. // VniDrop development and CI builds are intentionally unsigned.
// CODE_SIGNING_ALLOWED = NO
// Per-developer signing (e.g. DEVELOPMENT_TEAM) goes in Local.xcconfig, which is CODE_SIGNING_REQUIRED = NO
// gitignored. The optional include below means the build still works for anyone
// who doesn't have a Local.xcconfig — Xcode automatic signing fills in their team. // Signed local builds can opt in through this ignored file.
//
// To persist your team across `xcodegen generate`, create apple/Local.xcconfig:
// DEVELOPMENT_TEAM = XXXXXXXXXX
#include? "Local.xcconfig" #include? "Local.xcconfig"

View File

@@ -41,7 +41,6 @@ targets:
SWIFT_STRICT_CONCURRENCY: complete SWIFT_STRICT_CONCURRENCY: complete
ENABLE_USER_SCRIPT_SANDBOXING: NO ENABLE_USER_SCRIPT_SANDBOXING: NO
ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon
CODE_SIGN_STYLE: Automatic
configs: configs:
debug: debug:
CODE_SIGN_ENTITLEMENTS: VniDrop/Resources/VniDrop.entitlements CODE_SIGN_ENTITLEMENTS: VniDrop/Resources/VniDrop.entitlements
@@ -56,6 +55,9 @@ targets:
VniDropTests: VniDropTests:
type: bundle.unit-test type: bundle.unit-test
supportedDestinations: [iOS, macOS] supportedDestinations: [iOS, macOS]
configFiles:
Debug: Signing.xcconfig
Release: Signing.xcconfig
sources: sources:
- path: Tests - path: Tests
settings: settings:
@@ -63,7 +65,6 @@ targets:
GENERATE_INFOPLIST_FILE: YES GENERATE_INFOPLIST_FILE: YES
SWIFT_VERSION: "6.0" SWIFT_VERSION: "6.0"
SWIFT_STRICT_CONCURRENCY: complete SWIFT_STRICT_CONCURRENCY: complete
CODE_SIGN_STYLE: Automatic
dependencies: dependencies:
- target: VniDrop - target: VniDrop

35
config.mk Normal file
View File

@@ -0,0 +1,35 @@
# Default command configuration. Override locally in the ignored
# config.override.mk or on the command line (for example: make package-deb VERSION=1.2.0).
ifeq ($(OS),Windows_NT)
HOST_OS := windows
GRADLE ?= ./gradlew.bat
else
HOST_UNAME := $(shell uname -s)
ifeq ($(HOST_UNAME),Darwin)
HOST_OS := macos
else ifeq ($(HOST_UNAME),Linux)
HOST_OS := linux
else
HOST_OS := unknown
endif
GRADLE ?= ./gradlew
endif
CARGO ?= cargo
NPM ?= npm
BUN ?= bun
XCODEBUILD ?= xcodebuild
XCODEGEN ?= xcodegen
OPEN ?= open
POWERSHELL ?= pwsh
VERSION ?= $(shell sed -n 's/^vnidrop.version=//p' $(ROOT)/gradle.properties)
APPLE_PROFILE ?= debug
APPLE_CONFIGURATION ?= Debug
APPLE_DESTINATION ?=
APPLE_CODE_SIGNING ?= NO
APPLE_DERIVED_DATA ?= $(ROOT)/apple/DerivedData
GRADLE_FLAGS ?= --no-daemon --stacktrace
GRADLE_RELEASE_FLAGS ?= --no-daemon --no-configuration-cache --stacktrace

View File

@@ -19,20 +19,18 @@ Read [`CORE_FLOW.md`](CORE_FLOW.md) before changing send/receive/export/cancel.
Always prefer workspace commands so lockfile/fmt stay consistent: Always prefer workspace commands so lockfile/fmt stay consistent:
```bash ```bash
cargo fmt --all make format
cargo fmt --all -- --check make test-rust
cargo clippy --workspace --all-targets -- -D warnings make check-rust
cargo test -p vnidrop
cargo test --workspace --all-targets
``` ```
Focused integration suites: Focused integration suites:
```bash ```bash
cargo test -p vnidrop --test transfer make test-rust-transfer
cargo test -p vnidrop --test approval make test-rust-approval
cargo test -p vnidrop --test lifecycle make test-rust-lifecycle
cargo test -p vnidrop --test output_sink make test-rust-output-sink
``` ```
Docs (CI uses `-D warnings`): Docs (CI uses `-D warnings`):
@@ -41,7 +39,7 @@ Docs (CI uses `-D warnings`):
RUSTDOCFLAGS='-D warnings' cargo doc -p vnidrop --no-deps RUSTDOCFLAGS='-D warnings' cargo doc -p vnidrop --no-deps
``` ```
Run `cargo fmt --all` after finishing Rust edits without asking. Run `make format` after finishing Rust edits without asking.
--- ---
@@ -116,13 +114,11 @@ Details: [`tests/README.md`](tests/README.md).
## PR / verify checklist for this crate ## PR / verify checklist for this crate
```bash ```bash
cargo fmt --all -- --check make check-rust
cargo clippy --workspace --all-targets -- -D warnings
cargo test -p vnidrop
``` ```
If you touched cancel, export, or sinks, also: If you touched cancel, export, or sinks, also:
```bash ```bash
cargo test -p vnidrop --test output_sink make test-rust-output-sink
``` ```

View File

@@ -5,8 +5,8 @@ The product website for VniDrop, built with Next.js and exported as a static sit
## Local development ## Local development
```bash ```bash
npm install # From the repository root:
npm run dev make run-docs
``` ```
Open [http://localhost:3000](http://localhost:3000). Open [http://localhost:3000](http://localhost:3000).
@@ -14,9 +14,7 @@ Open [http://localhost:3000](http://localhost:3000).
## Checks ## Checks
```bash ```bash
npm run lint make check-docs
npm run typecheck
npm run build
``` ```
The production build is written to `out/` and can be hosted by any static web server. The production build is written to `out/` and can be hosted by any static web server.

View File

@@ -11,10 +11,10 @@ A Bun CLI generates the platform-native files from it:
## Workflow ## Workflow
```bash ```bash
cd localization # From the repository root:
bun run src/cli.ts validate # structural checks (run before committing) make check-localization # structural checks (run before committing)
bun run src/cli.ts generate # regenerate .xcstrings + strings.xml from strings.json make localization # regenerate .xcstrings + strings.xml from strings.json
bun run src/cli.ts migrate # one-time: rebuild strings.json from existing platform files make localization-migrate # one-time: rebuild strings.json from platform files
``` ```
**Never edit the generated `.xcstrings` / `strings.xml` by hand** — edit `strings.json` and **Never edit the generated `.xcstrings` / `strings.xml` by hand** — edit `strings.json` and

51
make/release.mk Normal file
View File

@@ -0,0 +1,51 @@
.PHONY: package-deb package-rpm package-msix
package-deb: ## Build and verify a Debian x64 package (VERSION=x.y.z).
@test "$(HOST_OS)" = linux || { printf 'Debian packaging requires Linux.\n' >&2; exit 1; }
@cd $(ROOT); \
version="$$(packaging/linux/resolve-version.sh "$(VERSION)")"; \
$(GRADLE) :shared:jvmTest :desktopApp:packageReleaseDeb \
-Pvnidrop.version="$$version" \
-Pvnidrop.desktop.rustVariant=release \
-Pvnidrop.diagnostics.included=false \
$(GRADLE_RELEASE_FLAGS); \
mapfile -t packages < <(find desktopApp/build/compose/binaries/main-release/deb -maxdepth 1 -type f -name '*.deb'); \
(( $${#packages[@]} == 1 )) || { printf 'Expected exactly one Debian package, found %s\n' "$${#packages[@]}" >&2; exit 1; }; \
output_directory=build/release/linux/deb; \
output_name="vnidrop_$${version}-1_amd64.deb"; \
mkdir -p "$$output_directory"; \
cp "$${packages[0]}" "$$output_directory/$$output_name"; \
packaging/linux/verify-package.sh deb "$$version" "$$output_directory/$$output_name"; \
( cd "$$output_directory" && sha256sum "$$output_name" > "$$output_name.sha256" ); \
printf 'Package: %s/%s\n' "$$output_directory" "$$output_name"
package-rpm: ## Build and verify an RPM x64 package (VERSION=x.y.z).
@test "$(HOST_OS)" = linux || { printf 'RPM packaging requires Linux.\n' >&2; exit 1; }
@cd $(ROOT); \
version="$$(packaging/linux/resolve-version.sh "$(VERSION)")"; \
$(GRADLE) :desktopApp:packageReleaseRpm \
-Pvnidrop.version="$$version" \
-Pvnidrop.desktop.rustVariant=release \
-Pvnidrop.diagnostics.included=false \
$(GRADLE_RELEASE_FLAGS); \
mapfile -t packages < <(find desktopApp/build/compose/binaries/main-release/rpm -maxdepth 1 -type f -name '*.rpm'); \
(( $${#packages[@]} == 1 )) || { printf 'Expected exactly one RPM package, found %s\n' "$${#packages[@]}" >&2; exit 1; }; \
output_directory=build/release/linux/rpm; \
output_name="vnidrop-$${version}-1.x86_64.rpm"; \
mkdir -p "$$output_directory"; \
cp "$${packages[0]}" "$$output_directory/$$output_name"; \
packaging/linux/verify-package.sh rpm "$$version" "$$output_directory/$$output_name"; \
( cd "$$output_directory" && sha256sum "$$output_name" > "$$output_name.sha256" ); \
printf 'Package: %s/%s\n' "$$output_directory" "$$output_name"
package-msix: ## Build and verify an unsigned Windows Store MSIX (VERSION=x.y.z).
@test "$(HOST_OS)" = windows || { printf 'MSIX packaging requires Windows.\n' >&2; exit 1; }
cd $(ROOT) && $(GRADLE) :shared:jvmTest :desktopApp:createReleaseDistributable \
-Pvnidrop.version="$(VERSION)" \
-Pvnidrop.desktop.rustVariant=release \
-Pvnidrop.diagnostics.included=false \
$(GRADLE_RELEASE_FLAGS)
cd $(ROOT) && $(POWERSHELL) -NoProfile -File packaging/windows/build-msix.ps1 \
-Version "$(VERSION)" \
-AppImage desktopApp/build/compose/binaries/main-release/app/VniDrop \
-OutputDirectory build/release/windows

View File

@@ -64,21 +64,10 @@ Ubuntu prevents `jpackage` from discovering normal RPM dependencies.
From the repository root on the matching Linux family, run one of: From the repository root on the matching Linux family, run one of:
```bash ```bash
./gradlew :shared:jvmTest :desktopApp:packageReleaseDeb \ make package-deb VERSION=1.0.0
-Pvnidrop.version=1.0.0 \ make package-rpm VERSION=1.0.0
-Pvnidrop.desktop.rustVariant=release \
-Pvnidrop.diagnostics.included=false \
--no-daemon --no-configuration-cache --stacktrace
./gradlew :shared:jvmTest :desktopApp:packageReleaseRpm \
-Pvnidrop.version=1.0.0 \
-Pvnidrop.desktop.rustVariant=release \
-Pvnidrop.diagnostics.included=false \
--no-daemon --no-configuration-cache --stacktrace
``` ```
Compose writes the packages under The Make targets collect the Compose output under `build/release/linux/`, then
`desktopApp/build/compose/binaries/main-release/deb/` and validate package identity, version, architecture, dependencies, bundled JVM,
`desktopApp/build/compose/binaries/main-release/rpm/`. The workflow then and release Rust payload before generating a SHA-256 checksum.
validates package identity, version, architecture, dependencies, bundled JVM,
and release Rust payload before publishing anything.

View File

@@ -85,19 +85,23 @@ npx wrangler r2 bucket create vnidrop-diagnostics
``` ```
Replace the placeholder `database_id` in `wrangler.jsonc` with the UUID returned Replace the placeholder `database_id` in `wrangler.jsonc` with the UUID returned
by `wrangler d1 create`. Set the ingest key interactively, apply the tracked D1 by `wrangler d1 create`. Set the ingest key interactively and configure the R2
migrations, and configure the R2 retention rule once: retention rule once:
```bash ```bash
npx wrangler secret put INGEST_KEY npx wrangler secret put INGEST_KEY
npm run db:migrate:remote
npx wrangler r2 bucket lifecycle add vnidrop-diagnostics diagnostics-retention --expire-days 90 npx wrangler r2 bucket lifecycle add vnidrop-diagnostics diagnostics-retention --expire-days 90
npm run check
npm run deploy
``` ```
`npm run deploy` also runs the complete `check` script automatically before Then apply migrations and deploy from the repository root:
Wrangler changes the remote Worker.
```bash
make diagnostics-db-remote
make deploy-diagnostics
```
`make deploy-diagnostics` runs the complete check before Wrangler changes the
remote Worker.
The lifecycle command changes the remote bucket. Before adding or changing a The lifecycle command changes the remote bucket. Before adding or changing a
rule, inspect the current state with: rule, inspect the current state with:
@@ -117,8 +121,9 @@ INGEST_KEY=local-development-only
Then initialize the local D1 database and run the Worker: Then initialize the local D1 database and run the Worker:
```bash ```bash
npm run db:migrate:local # From the repository root:
npm run dev make diagnostics-db-local
make run-diagnostics
``` ```
Wrangler keeps local D1 and R2 state under the ignored `.wrangler/` directory. Wrangler keeps local D1 and R2 state under the ignored `.wrangler/` directory.
@@ -133,7 +138,7 @@ Never edit an applied migration; add the next numbered SQL file instead.
bindings cannot silently drift from the Worker code: bindings cannot silently drift from the Worker code:
```bash ```bash
npm run typegen # regenerate after changing bindings or vars make diagnostics-typegen # from the repository root
npm run types:check # verify the committed file is current npm run types:check # verify the committed file is current
``` ```

View File

@@ -46,16 +46,15 @@ compose-skill “Existing Project Policy”: adapt to this repo; do not force-mi
From repo root: From repo root:
```bash ```bash
./gradlew :shared:jvmTest make check-shared
./gradlew :shared:compileKotlinJvm
``` ```
Optional: Optional:
```bash ```bash
./gradlew :shared:testAndroidHostTest make test-android-host
./gradlew :desktopApp:run make run-desktop
./gradlew :androidApp:assembleDebug make check-android
``` ```
CI `:shared:jvmTest` runs on **Linux**. Gobley host cargo follows the current CI `:shared:jvmTest` runs on **Linux**. Gobley host cargo follows the current
@@ -113,7 +112,7 @@ Never pass a directory as a single Android FD into `SourceKind.FILE_DESCRIPTOR`.
- Prefer fakes in `commonTest` support over real UniFFI in pure unit tests. - Prefer fakes in `commonTest` support over real UniFFI in pure unit tests.
```bash ```bash
./gradlew :shared:jvmTest make test-shared
``` ```
--- ---