#!/usr/bin/env nu # Cross-Platform Build Script for Nushell Plugins # Orchestrates cross-compilation for multiple platforms using Rust targets and Docker # Version check - mandatory for all plugin operations def version_check [] { try { nu scripts/check_version.nu --quiet | ignore } catch { print "āŒ Nushell version mismatch detected!" print "šŸ”§ Run: nu scripts/check_version.nu --fix" exit 1 } } # Load build targets configuration def load_targets_config [] { if not ("etc/build_targets.toml" | path exists) { error make {msg: "Build targets configuration not found: etc/build_targets.toml"} } open etc/build_targets.toml } # Get host platform information def get_host_info [] { let arch = match (uname -m) { "x86_64" => "amd64", "aarch64" => "arm64", "arm64" => "arm64", $arch if ($arch | str starts-with "arm") => "arm64", $other => $other } let platform = match (uname -s | str downcase) { $os if ($os | str contains "linux") => "linux", $os if ($os | str contains "darwin") => "darwin", $os if ($os | str contains "windows") => "windows", $other => $other } {platform: $platform, arch: $arch, full: $"($platform)-($arch)"} } # Validate if target can be built on current host def validate_target [target: record, host_info: record] { # Check if target requires specific host if ($target | get host_required? | default null) != null { if $target.host_required != $host_info.platform { return { valid: false, reason: $"Target requires ($target.host_required) host, but running on ($host_info.platform)" suggestion: "Use Docker build or switch to appropriate host" } } } # Check if native build is possible if $target.native_build and not $target.docker_required { # Check if rust target is installed let installed_targets = (rustup target list --installed | lines) if not ($target.rust_target in $installed_targets) { return { valid: false, reason: $"Rust target ($target.rust_target) not installed" suggestion: $"Run: rustup target add ($target.rust_target)" } } } {valid: true, reason: null, suggestion: null} } # Get all plugin directories def get_plugin_directories [] { glob "nu_plugin_*" | where ($it | path type) == "dir" } # Build a single plugin for a specific target def build_plugin_for_target [ plugin_dir: string, target: record, use_docker: bool = false, verbose: bool = false ] { print $"šŸ”Ø Building ($plugin_dir) for ($target.platform_name)..." let start_time = date now let original_dir = pwd try { cd $plugin_dir mut build_cmd = ["cargo", "build", "--release"] if not $use_docker { $build_cmd = ($build_cmd | append ["--target", $target.rust_target]) } # Set environment variables if specified let config = load_targets_config let env_vars = $config.environment? | default {} let target_env = $env_vars | get $target.platform_name? | default {} mut env_settings = [] for var in ($target_env | transpose key value) { $env_settings = ($env_settings | append $"($var.key)=($var.value)") } if $verbose { print $" Command: ($build_cmd | str join ' ')" if ($env_settings | length) > 0 { print $" Environment: ($env_settings | str join ', ')" } } # Execute build command if $use_docker { # Docker build will be handled by build_docker_cross.nu let docker_result = nu ../scripts/build_docker_cross.nu --target $target.platform_name --plugin $plugin_dir if $docker_result.exit_code != 0 { error make {msg: $"Docker build failed for ($plugin_dir)"} } } else { # Native cross-compilation with-env ($target_env) { run-external "cargo" ...$build_cmd } } cd $original_dir let end_time = date now let duration = $end_time - $start_time print $"āœ… Built ($plugin_dir) for ($target.platform_name) in ($duration)" { plugin: $plugin_dir, target: $target.platform_name, status: "success", duration: $duration, error: null } } catch {|err| cd $original_dir print $"āŒ Error building ($plugin_dir) for ($target.platform_name): ($err.msg)" { plugin: $plugin_dir, target: $target.platform_name, status: "error", duration: null, error: $err.msg } } } # Check if Docker is available and running def check_docker [] { try { docker --version | ignore docker info | ignore true } catch { false } } # Install missing Rust targets def install_rust_targets [targets: list] { print "šŸ¦€ Installing missing Rust targets..." let installed = (rustup target list --installed | lines) let missing = ($targets | where $it not-in $installed) if ($missing | length) > 0 { print $"šŸ“„ Installing targets: ($missing | str join ', ')" for target in $missing { print $" Installing ($target)..." rustup target add $target } print "āœ… All targets installed" } else { print "āœ… All required targets already installed" } } # Get binary path for a plugin and target def get_binary_path [plugin_dir: string, target: record] { let binary_name = if ($target | get binary_extension? | default "") != "" { $"($plugin_dir)($target.binary_extension)" } else { $plugin_dir } $"($plugin_dir)/target/($target.rust_target)/release/($binary_name)" } # Verify built binaries exist def verify_built_binaries [results: list] { print "šŸ” Verifying built binaries..." let config = load_targets_config mut verification_results = [] for result in $results { if $result.status == "success" { let target_config = $config.targets | get $result.target let binary_path = get_binary_path $result.plugin $target_config if ($binary_path | path exists) { let size = (ls $binary_path | get 0.size) print $" āœ… ($result.plugin) → ($result.target): ($size)" $verification_results = ($verification_results | append { plugin: $result.plugin, target: $result.target, binary_path: $binary_path, size: $size, verified: true }) } else { print $" āŒ ($result.plugin) → ($result.target): Binary not found at ($binary_path)" $verification_results = ($verification_results | append { plugin: $result.plugin, target: $result.target, binary_path: $binary_path, size: null, verified: false }) } } } $verification_results } # Main function def main [ --targets (-t): list = [] # Specific targets to build (e.g., linux-amd64, darwin-arm64) --all-targets (-a) # Build for all enabled targets --plugins (-p): list = [] # Specific plugins to build --docker (-d) # Force use of Docker for all builds --native (-n) # Force native compilation (fail if not possible) --parallel # Build plugins in parallel (experimental) --install-targets # Install missing Rust targets automatically --verbose (-v) # Verbose output --dry-run # Show what would be built without building --list-targets (-l) # List available targets and exit ] { # Mandatory version check version_check # Ensure we're in the repository root if not ("nu_plugin_clipboard" | path exists) { error make {msg: "Please run this script from the nushell-plugins repository root directory"} } print "šŸš€ Nushell Plugins Cross-Platform Build System" # Load configuration let config = load_targets_config let host_info = get_host_info print $"šŸ–„ļø Host: ($host_info.full)" # List targets mode if $list_targets { print "\nšŸ“‹ Available build targets:" for target_name in ($config.targets | transpose name config | get name) { let target_config = $config.targets | get $target_name let enabled = $target_config | get enabled? | default true let status = if $enabled { "āœ…" } else { "āŒ" } print $" ($status) ($target_name): ($target_config.description)" print $" Rust target: ($target_config.rust_target)" print $" Docker required: ($target_config.docker_required)" print $" Native build: ($target_config.native_build)" } return } # Determine targets to build let available_targets = $config.targets | transpose name config let enabled_targets = $available_targets | where ($it.config | get enabled? | default true) mut targets_to_build = if $all_targets { $enabled_targets } else if ($targets | length) > 0 { $enabled_targets | where $it.name in $targets } else { # Default to host platform if available let host_target = $enabled_targets | where $it.name == $host_info.full if ($host_target | length) > 0 { $host_target } else { # Fallback to first enabled target $enabled_targets | first 1 } } if ($targets_to_build | length) == 0 { print "ā“ No targets to build" if ($targets | length) > 0 { print $"šŸ’” Available targets: ($enabled_targets | get name | str join ', ')" } return } # Determine plugins to build let all_plugins = get_plugin_directories let plugins_to_build = if ($plugins | length) > 0 { $all_plugins | where $it in $plugins } else { $all_plugins } if ($plugins_to_build | length) == 0 { print "ā“ No plugins to build" return } print $"\nšŸ“¦ Plugins to build: ($plugins_to_build | length)" for plugin in $plugins_to_build { print $" - ($plugin)" } print $"\nšŸŽÆ Targets to build: ($targets_to_build | length)" for target in $targets_to_build { print $" - ($target.name): ($target.config.description)" } # Dry run mode if $dry_run { print "\nšŸ” Dry run - showing build matrix:" for plugin in $plugins_to_build { for target in $targets_to_build { let method = if $docker or $target.config.docker_required { "Docker" } else { "Native" } print $" ($plugin) Ɨ ($target.name) → ($method)" } } return } # Check prerequisites let docker_available = check_docker print $"\nšŸ”§ Prerequisites:" print $" Docker: (if $docker_available { 'āœ…' } else { 'āŒ' })" print $" Rust: āœ…" # Validate targets and collect required Rust targets mut rust_targets_needed = [] mut valid_builds = [] for target in $targets_to_build { let validation = validate_target $target.config $host_info let use_docker = $docker or $target.config.docker_required or (not $validation.valid) if $native and $use_docker { print $"āŒ Cannot build ($target.name) natively: ($validation.reason)" continue } if $use_docker and not $docker_available { print $"āŒ Docker required for ($target.name) but Docker not available" print $"šŸ’” ($validation.suggestion)" continue } if not $use_docker and $validation.valid { $rust_targets_needed = ($rust_targets_needed | append $target.config.rust_target) } $valid_builds = ($valid_builds | append { target: $target, use_docker: $use_docker, validation: $validation }) } # Install missing Rust targets if ($rust_targets_needed | length) > 0 and $install_targets { install_rust_targets $rust_targets_needed } # Build plugins print $"\nšŸ”Ø Starting builds..." let start_time = date now mut all_results = [] if $parallel { print "⚔ Building in parallel mode..." # Build all plugin-target combinations in parallel let build_jobs = [] for plugin in $plugins_to_build { for build in $valid_builds { $build_jobs = ($build_jobs | append { plugin: $plugin, target: $build.target.config, use_docker: $build.use_docker }) } } $all_results = ($build_jobs | par-each {|job| build_plugin_for_target $job.plugin $job.target $job.use_docker $verbose }) } else { # Sequential builds for plugin in $plugins_to_build { for build in $valid_builds { let result = build_plugin_for_target $plugin $build.target.config $build.use_docker $verbose $all_results = ($all_results | append $result) print "---" } } } let end_time = date now let total_duration = $end_time - $start_time # Verify binaries print "\nšŸ” Verification:" let verification = verify_built_binaries $all_results # Summary print "\nšŸ“Š Build Summary:" let successful = $all_results | where status == "success" let failed = $all_results | where status == "error" let verified = $verification | where verified == true print $"āœ… Successful builds: ($successful | length)" print $"āŒ Failed builds: ($failed | length)" print $"šŸ” Verified binaries: ($verified | length)" print $"ā±ļø Total time: ($total_duration)" if ($failed | length) > 0 { print "\nāŒ Failed builds:" for failure in $failed { print $" - ($failure.plugin) → ($failure.target): ($failure.error)" } } if ($verified | length) > 0 { print "\nāœ… Built binaries:" for binary in $verified { print $" - ($binary.plugin) → ($binary.target): ($binary.size)" } } # Exit with error if any builds failed if ($failed | length) > 0 { exit 1 } else { print "\nšŸŽ‰ All builds completed successfully!" print "šŸ’” Next steps:" print " - Collect binaries: just collect" print " - Create packages: just pack" print " - Test binaries: just test" } } if ($env.NUSHELL_EXECUTION_CONTEXT? | default "" | str contains "run") { main }