#!/usr/bin/env nu # Binary packaging tool - packages platform binaries for different architectures # # Packages: # - Cross-compiled binaries for multiple platforms # - Standalone executable packages # - Platform-specific installers # - Binary verification and signing use std log def main [ --source-dir: string = "dist/platform" # Source directory with compiled binaries --output-dir: string = "packages/binaries" # Output directory for packaged binaries --platforms: string = "linux-amd64,macos-amd64,windows-amd64" # Target platforms --format: string = "archive" # Package format: archive, installer, standalone --compress: bool = true # Compress binary packages --sign: bool = false # Sign binaries (requires signing keys) --verify: bool = true # Verify binary integrity --strip: bool = true # Strip debug symbols from release binaries --upx: bool = false # Use UPX compression --verbose: bool = false # Enable verbose logging ] -> record { let source_root = ($source_dir | path expand) let output_root = ($output_dir | path expand) let packaging_config = { source_dir: $source_root output_dir: $output_root platforms: ($platforms | split row "," | each { str trim }) format: $format compress: $compress sign: $sign verify: $verify strip: $strip upx: $upx verbose: $verbose } log info $"Starting binary packaging with config: ($packaging_config)" # Validate source directory if not ($source_root | path exists) { log error $"Source directory does not exist: ($source_root)" exit 1 } # Ensure output directory exists mkdir $output_root # Find available binaries let available_binaries = find_available_binaries $source_root $packaging_config if ($available_binaries | length) == 0 { log warning "No binaries found to package" return { status: "skipped" reason: "no binaries found" binaries_processed: 0 } } log info $"Found ($available_binaries | length) binaries to package" # Package binaries for each platform let packaging_results = [] for platform in $packaging_config.platforms { let platform_result = package_platform_binaries $platform $available_binaries $packaging_config let packaging_results = ($packaging_results | append $platform_result) } let summary = { total_platforms: ($packaging_config.platforms | length) successful_platforms: ($packaging_results | where status == "success" | length) failed_platforms: ($packaging_results | where status == "failed" | length) total_packages: ($packaging_results | get packages_created | math sum) total_size: ($packaging_results | get total_size | math sum) packaging_config: $packaging_config results: $packaging_results } if $summary.failed_platforms > 0 { log error $"Binary packaging completed with ($summary.failed_platforms) platform failures" exit 1 } else { log info $"Binary packaging completed successfully - ($summary.total_packages) packages created for ($summary.successful_platforms) platforms" } return $summary } # Find available binaries in source directory def find_available_binaries [ source_dir: string packaging_config: record ] -> list { # Find all executable files let executables = (find $source_dir -type f -executable) $executables | each {|binary| let binary_info = analyze_binary $binary $packaging_config { path: $binary name: ($binary | path basename) size: (ls $binary | get 0.size) architecture: $binary_info.architecture platform: $binary_info.platform format: $binary_info.format stripped: $binary_info.stripped } } } # Analyze binary file to determine its properties def analyze_binary [ binary_path: string packaging_config: record ] -> record { try { # Use file command to get binary information let file_info = (file $binary_path) let architecture = if ($file_info =~ "x86-64") or ($file_info =~ "x86_64") { "amd64" } else if ($file_info =~ "ARM64") or ($file_info =~ "aarch64") { "arm64" } else if ($file_info =~ "i386") { "i386" } else { "unknown" } let platform = if ($file_info =~ "Linux") { "linux" } else if ($file_info =~ "Mach-O") { "macos" } else if ($file_info =~ "PE32") { "windows" } else { "unknown" } let format = if ($file_info =~ "ELF") { "elf" } else if ($file_info =~ "Mach-O") { "macho" } else if ($file_info =~ "PE32") { "pe" } else { "unknown" } let stripped = ($file_info =~ "stripped") { architecture: $architecture platform: $platform format: $format stripped: $stripped } } catch { { architecture: "unknown" platform: "unknown" format: "unknown" stripped: false } } } # Package binaries for a specific platform def package_platform_binaries [ platform: string available_binaries: list packaging_config: record ] -> record { log info $"Packaging binaries for platform: ($platform)" let start_time = (date now) let platform_parts = ($platform | split row "-") let platform_os = ($platform_parts | get 0) let platform_arch = ($platform_parts | get 1) # Filter binaries for this platform let platform_binaries = ($available_binaries | where {|binary| ($binary.platform == $platform_os) and ($binary.architecture == $platform_arch) }) if ($platform_binaries | length) == 0 { log warning $"No binaries found for platform: ($platform)" return { platform: $platform status: "skipped" reason: "no binaries found" packages_created: 0 total_size: 0 duration: ((date now) - $start_time) } } let mut packaging_errors = [] let mut processed_binaries = [] let mut total_package_size = 0 # Process each binary for binary in $platform_binaries { let binary_result = process_single_binary $binary $platform $packaging_config if $binary_result.status == "success" { $processed_binaries = ($processed_binaries | append $binary_result) $total_package_size = $total_package_size + $binary_result.package_size } else { $packaging_errors = ($packaging_errors | append $binary_result.errors) } } # Create platform package let platform_package = create_platform_package $platform $processed_binaries $packaging_config let status = if (($packaging_errors | length) > 0) or ($platform_package.status != "success") { "failed" } else { "success" } { platform: $platform status: $status packages_created: ($processed_binaries | length) total_size: $total_package_size platform_package: $platform_package processed_binaries: $processed_binaries errors: $packaging_errors duration: ((date now) - $start_time) } } # Process a single binary def process_single_binary [ binary: record platform: string packaging_config: record ] -> record { if $packaging_config.verbose { log info $"Processing binary: ($binary.name) for ($platform)" } let start_time = (date now) let output_name = $"($binary.name)-($platform)" let temp_binary = ($packaging_config.output_dir | path join "tmp" $output_name) try { # Ensure temp directory exists mkdir ($temp_binary | path dirname) # Copy binary to temp location cp $binary.path $temp_binary # Strip debug symbols if requested and not already stripped if $packaging_config.strip and not $binary.stripped { strip_binary $temp_binary $packaging_config } # Apply UPX compression if requested if $packaging_config.upx { upx_compress_binary $temp_binary $packaging_config } # Verify binary integrity if $packaging_config.verify { let verification_result = verify_binary_integrity $temp_binary $packaging_config if $verification_result.status != "success" { return { binary: $binary.name status: "failed" reason: $"verification failed: ($verification_result.reason)" errors: [$verification_result] duration: ((date now) - $start_time) } } } # Sign binary if requested if $packaging_config.sign { let signing_result = sign_binary $temp_binary $packaging_config if $signing_result.status != "success" { return { binary: $binary.name status: "failed" reason: $"signing failed: ($signing_result.reason)" errors: [$signing_result] duration: ((date now) - $start_time) } } } # Package binary according to format let package_result = package_binary $temp_binary $output_name $packaging_config if $package_result.status == "success" { # Clean up temp file rm $temp_binary { binary: $binary.name status: "success" output_name: $output_name package_path: $package_result.package_path package_size: $package_result.package_size original_size: $binary.size compression_ratio: $package_result.compression_ratio duration: ((date now) - $start_time) } } else { { binary: $binary.name status: "failed" reason: $package_result.reason errors: [$package_result] duration: ((date now) - $start_time) } } } catch {|err| { binary: $binary.name status: "failed" reason: $err.msg errors: [{ error: $err.msg }] duration: ((date now) - $start_time) } } } # Strip debug symbols from binary def strip_binary [binary_path: string, packaging_config: record] { if $packaging_config.verbose { log info $"Stripping debug symbols: ($binary_path)" } try { # Use strip command (available on most Unix systems) strip $binary_path } catch {|err| log warning $"Failed to strip binary ($binary_path): ($err.msg)" } } # Compress binary with UPX def upx_compress_binary [binary_path: string, packaging_config: record] { if $packaging_config.verbose { log info $"UPX compressing: ($binary_path)" } try { # Check if UPX is available let upx_check = (which upx | complete) if $upx_check.exit_code != 0 { log warning "UPX not available, skipping compression" return } # Apply UPX compression upx --best $binary_path } catch {|err| log warning $"Failed to UPX compress binary ($binary_path): ($err.msg)" } } # Verify binary integrity def verify_binary_integrity [binary_path: string, packaging_config: record] -> record { try { # Check if binary is still executable let file_info = (file $binary_path) if not ($file_info =~ "executable") { return { status: "failed" reason: "binary is not executable after processing" } } # Try to run the binary with --help or --version let help_test = (run-external --redirect-combine $binary_path --help | complete) let version_test = (run-external --redirect-combine $binary_path --version | complete) if ($help_test.exit_code == 0) or ($version_test.exit_code == 0) { return { status: "success" verified: true } } else { return { status: "failed" reason: "binary does not respond to --help or --version" } } } catch {|err| return { status: "failed" reason: $err.msg } } } # Sign binary (placeholder - would need actual signing implementation) def sign_binary [binary_path: string, packaging_config: record] -> record { log warning "Binary signing not implemented - skipping" return { status: "success" signed: false reason: "signing not implemented" } } # Package binary according to specified format def package_binary [ binary_path: string output_name: string packaging_config: record ] -> record { match $packaging_config.format { "archive" => { create_archive_package $binary_path $output_name $packaging_config } "installer" => { create_installer_package $binary_path $output_name $packaging_config } "standalone" => { create_standalone_package $binary_path $output_name $packaging_config } _ => { { status: "failed" reason: $"unsupported format: ($packaging_config.format)" } } } } # Create archive package def create_archive_package [ binary_path: string output_name: string packaging_config: record ] -> record { let archive_name = $"($output_name).tar.gz" let archive_path = ($packaging_config.output_dir | path join $archive_name) try { # Create tar.gz archive let binary_dir = ($binary_path | path dirname) let binary_name = ($binary_path | path basename) cd $binary_dir tar -czf $archive_path $binary_name let archive_size = (ls $archive_path | get 0.size) let original_size = (ls $binary_path | get 0.size) let compression_ratio = (($archive_size | into float) / ($original_size | into float) * 100) { status: "success" package_path: $archive_path package_size: $archive_size compression_ratio: $compression_ratio } } catch {|err| { status: "failed" reason: $err.msg } } } # Create installer package def create_installer_package [ binary_path: string output_name: string packaging_config: record ] -> record { # Placeholder - would create platform-specific installers log warning "Installer packages not implemented - using archive format" create_archive_package $binary_path $output_name $packaging_config } # Create standalone package def create_standalone_package [ binary_path: string output_name: string packaging_config: record ] -> record { let standalone_path = ($packaging_config.output_dir | path join $output_name) try { # Just copy the binary as standalone cp $binary_path $standalone_path let package_size = (ls $standalone_path | get 0.size) { status: "success" package_path: $standalone_path package_size: $package_size compression_ratio: 100.0 } } catch {|err| { status: "failed" reason: $err.msg } } } # Create platform package (combines all binaries for a platform) def create_platform_package [ platform: string processed_binaries: list packaging_config: record ] -> record { if ($processed_binaries | length) == 0 { return { status: "skipped" reason: "no binaries to package" } } let platform_package_name = $"provisioning-binaries-($platform).tar.gz" let platform_package_path = ($packaging_config.output_dir | path join $platform_package_name) try { # Create temporary directory for platform package let temp_platform_dir = ($packaging_config.output_dir | path join "tmp" $"platform-($platform)") mkdir $temp_platform_dir # Copy all processed binaries to platform directory for binary in $processed_binaries { let binary_name = ($binary.package_path | path basename) cp $binary.package_path ($temp_platform_dir | path join $binary_name) } # Create platform archive let temp_parent = ($temp_platform_dir | path dirname) let temp_name = ($temp_platform_dir | path basename) cd $temp_parent tar -czf $platform_package_path $temp_name # Clean up temporary directory rm -rf $temp_platform_dir let package_size = (ls $platform_package_path | get 0.size) log info $"Created platform package: ($platform_package_path)" { status: "success" package_path: $platform_package_path package_size: $package_size binaries_included: ($processed_binaries | length) } } catch {|err| { status: "failed" reason: $err.msg } } } # Show binary information def "main info" [source_dir: string = "dist/platform"] { let source_root = ($source_dir | path expand) if not ($source_root | path exists) { return { error: "source directory not found", directory: $source_root } } let dummy_config = { verbose: false } let available_binaries = find_available_binaries $source_root $dummy_config { source_directory: $source_root total_binaries: ($available_binaries | length) binaries_by_platform: ($available_binaries | group-by platform | items {|platform, binaries| { platform: $platform, count: ($binaries | length) } }) binaries_by_architecture: ($available_binaries | group-by architecture | items {|arch, binaries| { architecture: $arch, count: ($binaries | length) } }) total_size: ($available_binaries | get size | math sum) binaries: $available_binaries } } # List packaged binaries def "main list" [packages_dir: string = "packages/binaries"] { let packages_root = ($packages_dir | path expand) if not ($packages_root | path exists) { return { error: "packages directory not found", directory: $packages_root } } let binary_packages = (find $packages_root -name "*.tar.gz" -o -name "provisioning-*" -type f) $binary_packages | each {|package| let package_info = (ls $package | get 0) { name: ($package | path basename) path: $package size: $package_info.size modified: $package_info.modified } } }