nushell-plugins/scripts/build_cross.nu
Jesús Pérez c5b510b939 feat: modularize justfile and fix collection/packaging scripts
- Modularize justfile system with dedicated modules:
  * alias.just: Command aliases (h, b, c, s)
  * build.just: Build and cross-compilation commands
  * distro.just: Collection and packaging commands
  * help.just: Comprehensive help system with areas
  * qa.just: Testing and quality assurance
  * tools.just: Development tools and utilities
  * upstream.just: Repository tracking and sync

- Fix platform detection in collect script:
  * Use actual platform names (darwin-arm64) instead of generic "host"
  * Support both "host" argument and auto-detection
  * Filter out .d dependency files from distribution

- Fix packaging script issues:
  * Correct uname command syntax (^uname -m)
  * Fix string interpolation and environment parsing
  * Include plugin binaries in archives (was only packaging metadata)
  * Use proper path join instead of string interpolation
  * Add --force flag to avoid interactive prompts

- Fix justfile absolute paths:
  * Replace relative paths with {{justfile_directory()}} function
  * Enable commands to work from any directory

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-20 19:04:08 +01:00

464 lines
15 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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
}