464 lines
15 KiB
Plaintext
464 lines
15 KiB
Plaintext
![]() |
#!/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
|
|||
|
}
|