
- 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>
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
|
||
} |