From a8a873c83dc59f679b7fa1618ec3f74a3903be55 Mon Sep 17 00:00:00 2001 From: Hammed Abass Date: Tue, 21 Jul 2026 18:07:06 +0200 Subject: [PATCH] build: unify project development commands --- .github/workflows/apple.yml | 28 ++-- .github/workflows/diagnostics-api.yml | 25 ++-- .github/workflows/docs.yml | 19 ++- .github/workflows/linux-packages.yml | 65 +-------- .github/workflows/rust-core.yml | 20 ++- .github/workflows/shared-kmp.yml | 10 +- .gitignore | 1 + AGENTS.md | 39 +++--- CONTRIBUTING.md | 51 +++++--- Makefile | 181 ++++++++++++++++++++++++++ README.md | 18 ++- apple/README.md | 29 +++-- apple/Signing.xcconfig | 13 +- apple/project.yml | 5 +- config.mk | 35 +++++ crates/vnidrop/AGENTS.md | 24 ++-- docs/README.md | 8 +- localization/README.md | 8 +- make/release.mk | 51 ++++++++ packaging/linux/README.md | 21 +-- services/diagnostics-api/README.md | 25 ++-- shared/AGENTS.md | 11 +- 22 files changed, 441 insertions(+), 246 deletions(-) create mode 100644 Makefile create mode 100644 config.mk create mode 100644 make/release.mk diff --git a/.github/workflows/apple.yml b/.github/workflows/apple.yml index 55e9d26..0157181 100644 --- a/.github/workflows/apple.yml +++ b/.github/workflows/apple.yml @@ -8,6 +8,9 @@ on: - "crates/uniffi-bindgen/**" - "Cargo.toml" - "Cargo.lock" + - "Makefile" + - "config.mk" + - "make/**" - ".github/workflows/apple.yml" push: branches: @@ -18,6 +21,9 @@ on: - "crates/uniffi-bindgen/**" - "Cargo.toml" - "Cargo.lock" + - "Makefile" + - "config.mk" + - "make/**" - ".github/workflows/apple.yml" permissions: @@ -57,23 +63,5 @@ jobs: - name: Install XcodeGen run: brew install xcodegen - - name: Build Rust core (xcframework + Swift bindings) - working-directory: 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 + - name: Build and test Apple app + run: make check-apple diff --git a/.github/workflows/diagnostics-api.yml b/.github/workflows/diagnostics-api.yml index b656860..59b00a2 100644 --- a/.github/workflows/diagnostics-api.yml +++ b/.github/workflows/diagnostics-api.yml @@ -4,12 +4,18 @@ on: pull_request: paths: - "services/diagnostics-api/**" + - "Makefile" + - "config.mk" + - "make/**" - ".github/workflows/diagnostics-api.yml" push: branches: - master paths: - "services/diagnostics-api/**" + - "Makefile" + - "config.mk" + - "make/**" - ".github/workflows/diagnostics-api.yml" permissions: @@ -23,9 +29,6 @@ jobs: quality: runs-on: ubuntu-latest timeout-minutes: 15 - defaults: - run: - working-directory: services/diagnostics-api steps: - name: Checkout uses: actions/checkout@v4 @@ -37,17 +40,5 @@ jobs: cache: npm cache-dependency-path: services/diagnostics-api/package-lock.json - - name: Install dependencies - run: npm ci - - - 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 + - name: Check diagnostics API + run: make check-diagnostics diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 8fa433c..ab2c4e8 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -4,12 +4,18 @@ on: pull_request: paths: - "docs/**" + - "Makefile" + - "config.mk" + - "make/**" - ".github/workflows/docs.yml" push: branches: - master paths: - "docs/**" + - "Makefile" + - "config.mk" + - "make/**" - ".github/workflows/docs.yml" permissions: @@ -23,9 +29,6 @@ jobs: quality: runs-on: ubuntu-latest timeout-minutes: 15 - defaults: - run: - working-directory: docs steps: - name: Checkout uses: actions/checkout@v4 @@ -37,11 +40,5 @@ jobs: cache: npm cache-dependency-path: docs/package-lock.json - - name: Install dependencies - run: npm ci - - - name: Type-check - run: npm run typecheck - - - name: Build - run: npm run build + - name: Check documentation website + run: make check-docs diff --git a/.github/workflows/linux-packages.yml b/.github/workflows/linux-packages.yml index 1e0c60a..1fa6f28 100644 --- a/.github/workflows/linux-packages.yml +++ b/.github/workflows/linux-packages.yml @@ -17,6 +17,9 @@ on: - "gradle.properties" - "gradle/**" - "gradlew" + - "Makefile" + - "config.mk" + - "make/**" push: tags: - "v*.*.*" @@ -92,37 +95,7 @@ jobs: echo "app=$version" >> "$GITHUB_OUTPUT" - name: Test and build Debian package - run: | - ./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" - ) + run: make package-deb VERSION=${{ steps.version.outputs.app }} - name: Upload Debian artifact if: github.event_name != 'pull_request' @@ -225,35 +198,7 @@ jobs: echo "app=$version" >> "$GITHUB_OUTPUT" - name: Build RPM package - run: | - ./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" - ) + run: make package-rpm VERSION=${{ steps.version.outputs.app }} - name: Upload RPM artifact if: github.event_name != 'pull_request' diff --git a/.github/workflows/rust-core.yml b/.github/workflows/rust-core.yml index 759ec1f..7eb59bc 100644 --- a/.github/workflows/rust-core.yml +++ b/.github/workflows/rust-core.yml @@ -6,6 +6,9 @@ on: - "Cargo.toml" - "Cargo.lock" - "crates/vnidrop/**" + - "Makefile" + - "config.mk" + - "make/**" - ".github/workflows/rust-core.yml" push: # Only after merge (or direct master pushes). Feature-branch work is covered @@ -16,6 +19,9 @@ on: - "Cargo.toml" - "Cargo.lock" - "crates/vnidrop/**" + - "Makefile" + - "config.mk" + - "make/**" - ".github/workflows/rust-core.yml" permissions: @@ -33,18 +39,10 @@ jobs: - uses: actions/checkout@v4 - name: Install Rust quality components run: rustup component add clippy rustfmt - - name: Check formatting - run: cargo fmt --all -- --check - - 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: Check Rust core + run: make check-rust - name: Install cargo-audit run: cargo install cargo-audit --locked - name: Audit Rust dependencies # Ignores are listed in .cargo/audit.toml for known transitive issues. - run: cargo audit + run: make audit-rust diff --git a/.github/workflows/shared-kmp.yml b/.github/workflows/shared-kmp.yml index 6c2fa73..3f94afb 100644 --- a/.github/workflows/shared-kmp.yml +++ b/.github/workflows/shared-kmp.yml @@ -15,6 +15,9 @@ on: - "gradle.properties" - "androidApp/**" - "desktopApp/**" + - "Makefile" + - "config.mk" + - "make/**" - ".github/workflows/shared-kmp.yml" push: branches: @@ -32,6 +35,9 @@ on: - "gradle.properties" - "androidApp/**" - "desktopApp/**" + - "Makefile" + - "config.mk" + - "make/**" - ".github/workflows/shared-kmp.yml" permissions: @@ -94,7 +100,7 @@ jobs: echo "ANDROID_NDK_ROOT=${{ steps.setup-ndk.outputs.ndk-path }}" >> "$GITHUB_ENV" - name: Run shared JVM tests - run: ./gradlew :shared:jvmTest --no-daemon --stacktrace + run: make check-shared - name: Verify Android native libraries - run: ./gradlew :androidApp:verifyDebugVnidropLibraries --no-daemon --stacktrace + run: make verify-android-libs diff --git a/.gitignore b/.gitignore index 1bab3e9..d3bdd2e 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ captures node_modules/ target/ .junie +config.override.mk # Local design export scratch output/ diff --git a/AGENTS.md b/AGENTS.md index 17912ac..b09f4a9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -53,7 +53,7 @@ Domain docs (reference, do not paste into PRs): ## 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. ### 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): ```bash -cargo fmt --all -- --check -cargo clippy --workspace --all-targets -- -D warnings -cargo test --workspace --all-targets +make check-rust ``` Focused: ```bash -cargo test -p vnidrop -cargo test -p vnidrop --test output_sink -cargo test -p vnidrop --test transfer -cargo test -p vnidrop --test approval -cargo test -p vnidrop --test lifecycle +make test-rust +make test-rust-output-sink +make test-rust-transfer +make test-rust-approval +make test-rust-lifecycle ``` After finishing Rust edits, format: ```bash -cargo fmt --all +make format ``` -CI also runs `cargo doc --workspace --no-deps` with `RUSTDOCFLAGS=-D warnings` -(see `.github/workflows/rust-core.yml`). Run it before large Rust public-API changes. +`make check-rust` includes documentation with warnings denied, matching +`.github/workflows/rust-core.yml`. ### Shared KMP / Compose (`shared/`) ```bash -./gradlew :shared:jvmTest -./gradlew :shared:compileKotlinJvm +make check-shared ``` Other targets (slower / machine-dependent): ```bash -./gradlew :shared:testAndroidHostTest -./gradlew :androidApp:assembleDebug -./gradlew :desktopApp:run +make test-android-host +make check-android +make run-desktop ``` **Note:** `jvmTest` CI runs on **Linux**. Gobley host cargo is enabled for the @@ -108,10 +105,10 @@ Rust library. | You changed… | Minimum verification | |--------------|----------------------| -| `crates/vnidrop/**` only | `cargo fmt`, `cargo clippy … -D warnings`, `cargo test -p vnidrop` | -| Cancel / export / sinks | Above + `cargo test -p vnidrop --test output_sink` | -| `shared/**` only | `./gradlew :shared:jvmTest` | -| Both | Rust suite + `:shared:jvmTest` | +| `crates/vnidrop/**` only | `make check-rust` | +| Cancel / export / sinks | Above + `make test-rust-output-sink` | +| `shared/**` only | `make test-shared` | +| Both | `make test-rust test-shared` | | Docs only | No suite required; verify links/paths | Do not kill long `cargo` / Gradle runs mid-flight unless they hang past several diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b7606a6..35f2b3f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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: +- GNU Make and Bash for the root command interface - JDK 17 or newer for Gradle and application builds - Rust stable with `rustfmt` and Clippy for the transfer core - 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 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 | Path | Purpose | @@ -87,55 +107,48 @@ Run checks from the repository root. Choose the suite for the files you changed. ### Rust Core ```bash -cargo fmt --all -cargo clippy --workspace --all-targets -- -D warnings -cargo test -p vnidrop +make format +make test-rust ``` For cancel, export, or output-sink changes, also run: ```bash -cargo test -p vnidrop --test output_sink +make test-rust-output-sink ``` For broader core changes, run the complete workspace suite: ```bash -cargo test --workspace --all-targets +make check-rust ``` ### Shared Kotlin and Compose ```bash -./gradlew :shared:jvmTest +make test-shared ``` Platform-specific checks may also be appropriate: ```bash -./gradlew :shared:testAndroidHostTest -./gradlew :androidApp:assembleDebug +make test-android-host +make check-android ``` ### Native Apple App ```bash -cd 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 +make check-apple ``` +Override the selected simulator when needed with +`make check-apple APPLE_DESTINATION='platform=iOS Simulator,name=iPhone 16'`. + ### Diagnostics Service ```bash -cd services/diagnostics-api -npm ci -npm run check +make check-diagnostics ``` If a required check cannot run on your machine, explain why in the pull request diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..5cf49b3 --- /dev/null +++ b/Makefile @@ -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 diff --git a/README.md b/README.md index e376c17..3172ea0 100644 --- a/README.md +++ b/README.md @@ -118,17 +118,21 @@ if you want to try the current version. git clone https://github.com/vnidrop/vnidrop.git cd vnidrop +# List the supported development commands and check prerequisites +make help +make doctor + # Windows/Linux desktop -./gradlew :desktopApp:run +make run-desktop # Android debug build -./gradlew :androidApp:assembleDebug +make build-android -# iOS, iPadOS, and macOS -cd apple -./scripts/build-core.sh debug -xcodegen generate -open VniDrop.xcodeproj +# Build and launch the macOS app +make open-apple + +# Open the native project for iOS, iPadOS, or Xcode development +make open-apple-project ``` See [`CONTRIBUTING.md`](CONTRIBUTING.md) for prerequisites, development setup, diff --git a/apple/README.md b/apple/README.md index 69f3a2f..4912d53 100644 --- a/apple/README.md +++ b/apple/README.md @@ -30,21 +30,24 @@ Prerequisites: Xcode, Rust with the Apple targets `aarch64-apple-darwin`), and `xcodegen` (`brew install xcodegen`). ```bash -# 1. Build the Rust core and generate the Swift bindings + xcframework. -apple/scripts/build-core.sh debug # or: release (see note below) - -# 2. Generate the Xcode project. -cd apple && xcodegen generate - -# 3. Open and run, or build from the CLI: -open VniDrop.xcodeproj -# 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 +# From the repository root: +make apple-core # Rust core, Swift bindings, and XCFramework +make apple-project # generate apple/VniDrop.xcodeproj +make open-apple-project # generate and open the project in Xcode +make build-apple-macos # unsigned macOS build +make open-apple # build and launch the macOS app +make build-apple-ios # unsigned iOS simulator build +make check-apple # iOS simulator tests ``` +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 `Package.swift` builds the same sources as a library (minus the `@main` entry), diff --git a/apple/Signing.xcconfig b/apple/Signing.xcconfig index 39d058f..da695ba 100644 --- a/apple/Signing.xcconfig +++ b/apple/Signing.xcconfig @@ -1,9 +1,6 @@ -// Committed signing config. Contains no secrets. -// -// Per-developer signing (e.g. DEVELOPMENT_TEAM) goes in Local.xcconfig, which is -// 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. -// -// To persist your team across `xcodegen generate`, create apple/Local.xcconfig: -// DEVELOPMENT_TEAM = XXXXXXXXXX +// VniDrop development and CI builds are intentionally unsigned. +CODE_SIGNING_ALLOWED = NO +CODE_SIGNING_REQUIRED = NO + +// Signed local builds can opt in through this ignored file. #include? "Local.xcconfig" diff --git a/apple/project.yml b/apple/project.yml index 3f643f6..ab0d4c5 100644 --- a/apple/project.yml +++ b/apple/project.yml @@ -41,7 +41,6 @@ targets: SWIFT_STRICT_CONCURRENCY: complete ENABLE_USER_SCRIPT_SANDBOXING: NO ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon - CODE_SIGN_STYLE: Automatic configs: debug: CODE_SIGN_ENTITLEMENTS: VniDrop/Resources/VniDrop.entitlements @@ -56,6 +55,9 @@ targets: VniDropTests: type: bundle.unit-test supportedDestinations: [iOS, macOS] + configFiles: + Debug: Signing.xcconfig + Release: Signing.xcconfig sources: - path: Tests settings: @@ -63,7 +65,6 @@ targets: GENERATE_INFOPLIST_FILE: YES SWIFT_VERSION: "6.0" SWIFT_STRICT_CONCURRENCY: complete - CODE_SIGN_STYLE: Automatic dependencies: - target: VniDrop diff --git a/config.mk b/config.mk new file mode 100644 index 0000000..1d14e66 --- /dev/null +++ b/config.mk @@ -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 diff --git a/crates/vnidrop/AGENTS.md b/crates/vnidrop/AGENTS.md index ec71e19..da2c853 100644 --- a/crates/vnidrop/AGENTS.md +++ b/crates/vnidrop/AGENTS.md @@ -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: ```bash -cargo fmt --all -cargo fmt --all -- --check -cargo clippy --workspace --all-targets -- -D warnings -cargo test -p vnidrop -cargo test --workspace --all-targets +make format +make test-rust +make check-rust ``` Focused integration suites: ```bash -cargo test -p vnidrop --test transfer -cargo test -p vnidrop --test approval -cargo test -p vnidrop --test lifecycle -cargo test -p vnidrop --test output_sink +make test-rust-transfer +make test-rust-approval +make test-rust-lifecycle +make test-rust-output-sink ``` Docs (CI uses `-D warnings`): @@ -41,7 +39,7 @@ Docs (CI uses `-D warnings`): 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 ```bash -cargo fmt --all -- --check -cargo clippy --workspace --all-targets -- -D warnings -cargo test -p vnidrop +make check-rust ``` If you touched cancel, export, or sinks, also: ```bash -cargo test -p vnidrop --test output_sink +make test-rust-output-sink ``` diff --git a/docs/README.md b/docs/README.md index ef71cba..7ffe337 100644 --- a/docs/README.md +++ b/docs/README.md @@ -5,8 +5,8 @@ The product website for VniDrop, built with Next.js and exported as a static sit ## Local development ```bash -npm install -npm run dev +# From the repository root: +make run-docs ``` Open [http://localhost:3000](http://localhost:3000). @@ -14,9 +14,7 @@ Open [http://localhost:3000](http://localhost:3000). ## Checks ```bash -npm run lint -npm run typecheck -npm run build +make check-docs ``` The production build is written to `out/` and can be hosted by any static web server. diff --git a/localization/README.md b/localization/README.md index d277173..14111e5 100644 --- a/localization/README.md +++ b/localization/README.md @@ -11,10 +11,10 @@ A Bun CLI generates the platform-native files from it: ## Workflow ```bash -cd localization -bun run src/cli.ts validate # structural checks (run before committing) -bun run src/cli.ts generate # regenerate .xcstrings + strings.xml from strings.json -bun run src/cli.ts migrate # one-time: rebuild strings.json from existing platform files +# From the repository root: +make check-localization # structural checks (run before committing) +make localization # regenerate .xcstrings + strings.xml from strings.json +make localization-migrate # one-time: rebuild strings.json from platform files ``` **Never edit the generated `.xcstrings` / `strings.xml` by hand** — edit `strings.json` and diff --git a/make/release.mk b/make/release.mk new file mode 100644 index 0000000..27680a6 --- /dev/null +++ b/make/release.mk @@ -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 diff --git a/packaging/linux/README.md b/packaging/linux/README.md index 53a3030..4cf8244 100644 --- a/packaging/linux/README.md +++ b/packaging/linux/README.md @@ -64,21 +64,10 @@ Ubuntu prevents `jpackage` from discovering normal RPM dependencies. From the repository root on the matching Linux family, run one of: ```bash -./gradlew :shared:jvmTest :desktopApp:packageReleaseDeb \ - -Pvnidrop.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 +make package-deb VERSION=1.0.0 +make package-rpm VERSION=1.0.0 ``` -Compose writes the packages under -`desktopApp/build/compose/binaries/main-release/deb/` and -`desktopApp/build/compose/binaries/main-release/rpm/`. The workflow then -validates package identity, version, architecture, dependencies, bundled JVM, -and release Rust payload before publishing anything. +The Make targets collect the Compose output under `build/release/linux/`, then +validate package identity, version, architecture, dependencies, bundled JVM, +and release Rust payload before generating a SHA-256 checksum. diff --git a/services/diagnostics-api/README.md b/services/diagnostics-api/README.md index 3e77b7b..3f48f08 100644 --- a/services/diagnostics-api/README.md +++ b/services/diagnostics-api/README.md @@ -85,19 +85,23 @@ npx wrangler r2 bucket create vnidrop-diagnostics ``` 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 -migrations, and configure the R2 retention rule once: +by `wrangler d1 create`. Set the ingest key interactively and configure the R2 +retention rule once: ```bash npx wrangler secret put INGEST_KEY -npm run db:migrate:remote 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 -Wrangler changes the remote Worker. +Then apply migrations and deploy from the repository root: + +```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 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: ```bash -npm run db:migrate:local -npm run dev +# From the repository root: +make diagnostics-db-local +make run-diagnostics ``` 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: ```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 ``` diff --git a/shared/AGENTS.md b/shared/AGENTS.md index 5428234..6add30a 100644 --- a/shared/AGENTS.md +++ b/shared/AGENTS.md @@ -46,16 +46,15 @@ compose-skill “Existing Project Policy”: adapt to this repo; do not force-mi From repo root: ```bash -./gradlew :shared:jvmTest -./gradlew :shared:compileKotlinJvm +make check-shared ``` Optional: ```bash -./gradlew :shared:testAndroidHostTest -./gradlew :desktopApp:run -./gradlew :androidApp:assembleDebug +make test-android-host +make run-desktop +make check-android ``` 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. ```bash -./gradlew :shared:jvmTest +make test-shared ``` ---