use clap::{Args, Subcommand};
use std::path::{Path, PathBuf};
#[derive(Args)]
pub struct CiArgs {
#[command(subcommand)]
pub command: CiCommand,
}
#[derive(Subcommand)]
pub enum CiCommand {
Init(CiInitArgs),
}
#[derive(Args)]
pub struct CiInitArgs {
#[arg(short, long, default_value = ".")]
pub dir: String,
#[arg(long)]
pub force: bool,
}
pub fn run(args: CiArgs) -> Result<(), Box<dyn std::error::Error>> {
match args.command {
CiCommand::Init(args) => init(args),
}
}
fn init(args: CiInitArgs) -> Result<(), Box<dyn std::error::Error>> {
let project_dir = Path::new(&args.dir);
if !project_dir.join("nativ.toml").is_file() {
return Err(format!("nativ.toml not found in {}", project_dir.display()).into());
}
let workflow = workflow_path(project_dir);
if workflow.exists() && !args.force {
return Err(format!(
"{} already exists (pass --force to overwrite)",
workflow.display()
)
.into());
}
if let Some(parent) = workflow.parent() {
std::fs::create_dir_all(parent)?;
}
std::fs::write(&workflow, github_actions_workflow())?;
println!("CI workflow written to {}", workflow.display());
Ok(())
}
fn workflow_path(project_dir: &Path) -> PathBuf {
project_dir
.join(".github")
.join("workflows")
.join("nativ.yml")
}
fn github_actions_workflow() -> &'static str {
r#"name: Nativ CI
on:
pull_request:
push:
branches: [main]
workflow_dispatch:
inputs:
submit:
description: Optional store submission through user-owned Fastlane lanes
type: choice
default: none
options:
- none
- ios
- android
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- name: Install Nativ
run: cargo install nativ --locked
- name: Check Nativ sources
run: nativ check --dir .
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- name: Install Nativ
run: cargo install nativ --locked
- name: Build web preview
run: nativ build --web --dir .
build-android:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "17"
- uses: gradle/actions/setup-gradle@v4
with:
gradle-version: "8.7"
- name: Install Nativ
run: cargo install nativ --locked
- name: Generate Android project
run: nativ build --android --dir .
- name: Compile Android debug APK
working-directory: build/android
run: gradle :app:assembleDebug
build-ios:
runs-on: macos-14
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- name: Install Nativ
run: cargo install nativ --locked
- name: Generate iOS project
run: nativ build --ios --dir .
- name: Compile iOS simulator app
run: |
PROJECT=$(ls build/ios/*.xcodeproj | head -n 1)
SCHEME=$(basename "$PROJECT" .xcodeproj)
xcodebuild build \
-project "$PROJECT" \
-scheme "$SCHEME" \
-destination 'generic/platform=iOS Simulator' \
CODE_SIGNING_ALLOWED=NO
submit-ios:
if: ${{ github.event_name == 'workflow_dispatch' && inputs.submit == 'ios' }}
needs: build-ios
runs-on: macos-14
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: ruby/setup-ruby@v1
with:
ruby-version: "3.3"
- name: Install Nativ
run: cargo install nativ --locked
- name: Install Fastlane
run: gem install fastlane
- name: Submit iOS
run: nativ submit ios --lane beta --dir .
submit-android:
if: ${{ github.event_name == 'workflow_dispatch' && inputs.submit == 'android' }}
needs: build-android
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "17"
- uses: gradle/actions/setup-gradle@v4
with:
gradle-version: "8.7"
- uses: ruby/setup-ruby@v1
with:
ruby-version: "3.3"
- name: Install Nativ
run: cargo install nativ --locked
- name: Install Fastlane
run: gem install fastlane
- name: Submit Android
run: nativ submit android --lane internal --dir .
"#
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn workflow_includes_native_build_jobs() {
let workflow = github_actions_workflow();
assert!(workflow.contains("nativ check --dir ."));
assert!(workflow.contains("nativ build --web --dir ."));
assert!(workflow.contains("nativ build --android --dir ."));
assert!(workflow.contains("gradle-version: \"8.7\""));
assert!(workflow.contains("gradle :app:assembleDebug"));
assert!(workflow.contains("nativ build --ios --dir ."));
assert!(workflow.contains("xcodebuild build"));
assert!(workflow.contains("workflow_dispatch"));
assert!(workflow.contains("nativ submit ios --lane beta --dir ."));
assert!(workflow.contains("nativ submit android --lane internal --dir ."));
}
#[test]
fn workflow_path_uses_github_actions_default() {
assert_eq!(
workflow_path(Path::new("Demo")),
Path::new("Demo")
.join(".github")
.join("workflows")
.join("nativ.yml")
);
}
}