nushell-plugins/scripts/build_docker_cross.nu

476 lines
15 KiB
Plaintext
Raw Normal View History

#!/usr/bin/env nu
# Docker Cross-Compilation Manager for Nushell Plugins
# Manages Docker-based cross-compilation environment and builds
# 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
}
# Check if Docker is available and running
def check_docker [] {
try {
let version = (docker --version | complete)
if $version.exit_code != 0 {
return {available: false, reason: "Docker command failed"}
}
let info = (docker info | complete)
if $info.exit_code != 0 {
return {available: false, reason: "Docker daemon not running"}
}
{available: true, reason: null}
} catch {|err|
{available: false, reason: $"Docker not found: ($err.msg)"}
}
}
# Build the Docker cross-compilation image
def build_docker_image [
--force (-f) # Force rebuild of image
--no-cache # Don't use Docker build cache
--verbose (-v) # Verbose output
] {
let config = load_targets_config
let docker_config = $config.docker
let dockerfile = $docker_config.dockerfile
let image_tag = $docker_config.image_tag
print $"🐳 Building Docker cross-compilation image..."
print $"📄 Dockerfile: ($dockerfile)"
print $"🏷️ Tag: ($image_tag)"
# Check if Dockerfile exists
if not ($dockerfile | path exists) {
error make {msg: $"Dockerfile not found: ($dockerfile)"}
}
# Check if image already exists
let existing_image = (docker images $image_tag --format "{{.Repository}}:{{.Tag}}" | complete)
if $existing_image.exit_code == 0 and ($existing_image.stdout | str trim | str length) > 0 and not $force {
print $"✅ Docker image already exists: ($image_tag)"
print "💡 Use --force to rebuild or --no-cache for fresh build"
return {success: true, image: $image_tag, rebuilt: false}
}
let start_time = date now
try {
mut build_args = ["docker", "build", "-f", $dockerfile, "-t", $image_tag]
if $no_cache {
$build_args = ($build_args | append "--no-cache")
}
# Add cache-from if specified
if ($docker_config.cache_from? | default [] | length) > 0 {
for cache in $docker_config.cache_from {
$build_args = ($build_args | append ["--cache-from", $cache])
}
}
# Add build args if specified
if ($docker_config.build_args? | default [] | length) > 0 {
for arg in $docker_config.build_args {
$build_args = ($build_args | append ["--build-arg", $arg])
}
}
$build_args = ($build_args | append ".")
if $verbose {
print $"🔨 Build command: ($build_args | str join ' ')"
}
print "🔨 Building Docker image (this may take several minutes)..."
let result = (run-external "docker" ...($build_args | skip 1))
let end_time = date now
let duration = $end_time - $start_time
print $"✅ Docker image built successfully: ($image_tag)"
print $"⏱️ Build time: ($duration)"
# Show image info
let image_info = (docker images $image_tag --format "table {{.Repository}}:{{.Tag}}\t{{.Size}}\t{{.CreatedAt}}")
print $"📊 Image info:"
print $image_info
{success: true, image: $image_tag, rebuilt: true, duration: $duration}
} catch {|err|
print $"❌ Failed to build Docker image: ($err.msg)"
{success: false, image: null, error: $err.msg}
}
}
# Get all plugin directories
def get_plugin_directories [] {
glob "nu_plugin_*" | where ($it | path type) == "dir"
}
# Build a single plugin in Docker for a specific target
def build_plugin_in_docker [
plugin_dir: string,
target_name: string,
image_tag: string,
verbose: bool = false
] {
let config = load_targets_config
let target_config = $config.targets | get $target_name
print $"🐳 Building ($plugin_dir) for ($target_name) in Docker..."
let start_time = date now
let work_dir = "/workspace"
let source_dir = $"/workspace/($plugin_dir)"
# Prepare Docker run command
mut docker_args = [
"docker", "run", "--rm",
"-v", $"(pwd):($work_dir)",
"-w", $source_dir,
$image_tag
]
# Set environment variables for the target
let env_vars = $config.environment? | default {}
let target_env = $env_vars | get $target_name? | default {}
for var in ($target_env | transpose key value) {
$docker_args = ($docker_args | append ["-e", $"($var.key)=($var.value)"])
}
# Add common environment variables
$docker_args = ($docker_args | append [
"-e", "RUST_BACKTRACE=1",
"-e", "CARGO_INCREMENTAL=0"
])
# Build command
let build_cmd = ["cargo", "build", "--release", "--target", $target_config.rust_target]
$docker_args = ($docker_args | append $build_cmd)
try {
if $verbose {
print $" 🔨 Docker command: ($docker_args | str join ' ')"
}
let result = (run-external "docker" ...($docker_args | skip 1))
let end_time = date now
let duration = $end_time - $start_time
# Check if binary was created
let binary_name = if ($target_config | get binary_extension? | default "") != "" {
$"($plugin_dir)($target_config.binary_extension)"
} else {
$plugin_dir
}
let binary_path = $"($plugin_dir)/target/($target_config.rust_target)/release/($binary_name)"
if ($binary_path | path exists) {
let size = (ls $binary_path | get 0.size)
print $"✅ Built ($plugin_dir) for ($target_name) in ($duration) → ($size)"
{
plugin: $plugin_dir,
target: $target_name,
status: "success",
duration: $duration,
binary_path: $binary_path,
size: $size,
error: null
}
} else {
error make {msg: $"Binary not found at expected path: ($binary_path)"}
}
} catch {|err|
let end_time = date now
let duration = $end_time - $start_time
print $"❌ Error building ($plugin_dir) for ($target_name): ($err.msg)"
{
plugin: $plugin_dir,
target: $target_name,
status: "error",
duration: $duration,
binary_path: null,
size: null,
error: $err.msg
}
}
}
# Clean up Docker build artifacts
def cleanup_docker [
--images (-i) # Remove Docker images
--containers (-c) # Remove stopped containers
--volumes (-v) # Remove unused volumes
--all (-a) # Clean everything
] {
print "🧹 Cleaning up Docker artifacts..."
if $all or $containers {
print "🗑️ Removing stopped containers..."
try {
docker container prune -f
} catch {
print "⚠️ No containers to remove"
}
}
if $all or $volumes {
print "🗑️ Removing unused volumes..."
try {
docker volume prune -f
} catch {
print "⚠️ No volumes to remove"
}
}
if $all or $images {
print "🗑️ Removing unused images..."
try {
docker image prune -f
} catch {
print "⚠️ No images to remove"
}
}
print "✅ Docker cleanup completed"
}
# Show Docker environment information
def show_docker_info [] {
let docker_status = check_docker
print "🐳 Docker Environment Information"
print $"Status: (if $docker_status.available { '✅ Available' } else { '❌ Not available' })"
if not $docker_status.available {
print $"Reason: ($docker_status.reason)"
return
}
try {
print "\n📊 Docker Version:"
docker --version
print "\n💾 Docker System Info:"
docker system df
print "\n🖼 Available Images:"
docker images --format "table {{.Repository}}:{{.Tag}}\t{{.Size}}\t{{.CreatedAt}}"
let config = load_targets_config
let image_tag = $config.docker.image_tag
print $"\n🎯 Cross-compilation Image Status:"
let image_exists = (docker images $image_tag --format "{{.Repository}}:{{.Tag}}" | complete)
if $image_exists.exit_code == 0 and ($image_exists.stdout | str trim | str length) > 0 {
print $"✅ Image exists: ($image_tag)"
let image_info = (docker images $image_tag --format "table {{.Size}}\t{{.CreatedAt}}")
print $image_info
} else {
print $"❌ Image not found: ($image_tag)"
print "💡 Run with --build-image to create it"
}
} catch {|err|
print $"❌ Error getting Docker info: ($err.msg)"
}
}
# Main function
def main [
--target (-t): string = "" # Specific target to build for
--plugin (-p): string = "" # Specific plugin to build
--build-image (-b) # Build Docker image before building
--force-rebuild (-f) # Force rebuild of Docker image
--no-cache # Don't use Docker build cache
--parallel # Build plugins in parallel
--cleanup # Clean up Docker artifacts after build
--info (-i) # Show Docker environment info
--verbose (-v) # Verbose output
--dry-run # Show what would be built
] {
# 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 "🐳 Docker Cross-Compilation Manager for Nushell Plugins"
# Check Docker availability
let docker_status = check_docker
if not $docker_status.available {
print $"❌ Docker not available: ($docker_status.reason)"
print "💡 Please install and start Docker to use cross-compilation"
exit 1
}
# Info mode
if $info {
show_docker_info
return
}
let config = load_targets_config
let image_tag = $config.docker.image_tag
# Build Docker image if requested or if it doesn't exist
let image_exists = (docker images $image_tag --format "{{.Repository}}:{{.Tag}}" | complete)
let should_build_image = $build_image or $force_rebuild or (
$image_exists.exit_code != 0 or ($image_exists.stdout | str trim | str length) == 0
)
if $should_build_image {
let build_result = build_docker_image --force=$force_rebuild --no-cache=$no_cache --verbose=$verbose
if not $build_result.success {
print "❌ Failed to build Docker image, cannot proceed"
exit 1
}
}
# Determine targets and plugins
let available_targets = $config.targets | transpose name config | where ($it.config | get enabled? | default true)
let docker_targets = $available_targets | where $it.config.docker_required
let targets_to_build = if ($target | str length) > 0 {
let selected = $docker_targets | where $it.name == $target
if ($selected | length) == 0 {
print $"❌ Target not found or doesn't require Docker: ($target)"
print $"💡 Available Docker targets: ($docker_targets | get name | str join ', ')"
exit 1
}
$selected
} else {
$docker_targets
}
let all_plugins = get_plugin_directories
let plugins_to_build = if ($plugin | str length) > 0 {
let selected = $all_plugins | where $it == $plugin
if ($selected | length) == 0 {
print $"❌ Plugin not found: ($plugin)"
print $"💡 Available plugins: ($all_plugins | str join ', ')"
exit 1
}
$selected
} else {
$all_plugins
}
if ($targets_to_build | length) == 0 {
print "❓ No Docker targets to build"
print $"💡 Available Docker targets: ($docker_targets | get name | str join ', ')"
return
}
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🎯 Docker targets: ($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 {
print $" 🐳 ($plugin) × ($target.name)"
}
}
return
}
# Build plugins
print $"\n🔨 Starting Docker builds..."
let start_time = date now
mut all_results = []
if $parallel {
print "⚡ Building in parallel mode..."
let build_jobs = []
for plugin in $plugins_to_build {
for target in $targets_to_build {
$build_jobs = ($build_jobs | append {
plugin: $plugin,
target: $target.name
})
}
}
$all_results = ($build_jobs | par-each {|job|
build_plugin_in_docker $job.plugin $job.target $image_tag $verbose
})
} else {
# Sequential builds
for plugin in $plugins_to_build {
for target in $targets_to_build {
let result = build_plugin_in_docker $plugin $target.name $image_tag $verbose
$all_results = ($all_results | append $result)
print "---"
}
}
}
let end_time = date now
let total_duration = $end_time - $start_time
# Summary
print "\n📊 Docker Build Summary:"
let successful = $all_results | where status == "success"
let failed = $all_results | where status == "error"
print $"✅ Successful builds: ($successful | length)"
print $"❌ Failed builds: ($failed | length)"
print $"⏱️ Total time: ($total_duration)"
if ($successful | length) > 0 {
print "\n✅ Built binaries:"
for success in $successful {
print $" - ($success.plugin) → ($success.target): ($success.size)"
}
}
if ($failed | length) > 0 {
print "\n❌ Failed builds:"
for failure in $failed {
print $" - ($failure.plugin) → ($failure.target): ($failure.error)"
}
}
# Cleanup if requested
if $cleanup {
print ""
cleanup_docker --containers --volumes
}
# Exit with error if any builds failed
if ($failed | length) > 0 {
exit 1
} else {
print "\n🎉 All Docker builds completed successfully!"
print "💡 Binaries are available in plugin/target/RUST_TARGET/release/ directories"
}
}
if ($env.NUSHELL_EXECUTION_CONTEXT? | default "" | str contains "run") {
main
}