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