nushell-plugins/scripts/build_cross.nu

464 lines
15 KiB
Plaintext
Raw Permalink Normal View History

#!/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<string>] {
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<record>] {
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<string> = [] # Specific targets to build (e.g., linux-amd64, darwin-arm64)
--all-targets (-a) # Build for all enabled targets
--plugins (-p): list<string> = [] # 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
}