<# .SYNOPSIS 제3자 의존성 벤더링 (NETWORKED 빌드 머신 전용) — firmware/third_party 채우기. .DESCRIPTION 폐쇄망(air-gapped) 타깃으로 전달하기 위해, 빌드/스테이징 머신에서 핀 고정된 제3자 의존성을 내려받아 SHA-256 으로 무결성을 검증한 뒤 firmware/third_party 아래에 펼친다. **네트워크가 허용되는 유일한 단계**다. 이후 build.ps1 은 네트워크 없이 빌드한다. 벤더링 대상(핀 고정): - STM32CubeF4 (HAL + CMSIS + startup/system) TODO(vendor): 버전 핀 확정 - FreeRTOS-Kernel (네이티브 API, ARM_CM4F 포트) TODO(vendor) - lwip (LwIP TCP/IP + apps/sntp) TODO(vendor) - mbedtls (TLS 1.2 클라이언트) TODO(vendor) 중요: 아래 $Deps 의 Url/Sha256/Tag 는 운영 환경에서 **사용할 정확한 핀 버전**으로 교체해야 한다. SHA-256 미설정('TODO') 인 항목은 다운로드 후 계산된 해시를 출력만 하고(최초 1회 기록용) 검증은 건너뛴다 — 기록 후 반드시 채워 넣을 것. .PARAMETER Force 이미 존재하는 third_party 하위 트리를 덮어쓴다(재벤더링). .PARAMETER KeepArchives 내려받은 압축 파일을 third_party/_archives 에 보존한다(오프라인 재현용). .EXAMPLE pwsh firmware/scripts/vendor.ps1 pwsh firmware/scripts/vendor.ps1 -Force -KeepArchives #> [CmdletBinding()] param( [switch]$Force, [switch]$KeepArchives ) $ErrorActionPreference = 'Stop' [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path $FwRoot = Split-Path -Parent $ScriptDir $ThirdParty = Join-Path $FwRoot 'third_party' $ArchiveDir = Join-Path $ThirdParty '_archives' # ============================================================================= # 핀 고정 의존성 목록 # Name : third_party 아래 디렉터리명 (CMakeLists 의 경로와 일치해야 함) # Tag : 릴리스/태그(추적용) # Url : 다운로드 URL (release tarball/zip) # Sha256 : 압축 파일 SHA-256 (소문자 hex). 'TODO' 면 검증 생략(해시 출력만). # Strip : 압축 최상위 폴더를 벗겨낼지(대부분 release 는 단일 최상위 폴더) # # TODO(vendor): Url/Sha256/Tag 를 운영에서 쓸 정확한 핀 버전으로 교체. # STM32CubeF4 는 GitHub release zip 또는 ST 공식 배포본을 사용한다. # ============================================================================= $Deps = @( @{ Name = 'STM32CubeF4' Tag = 'v1.28.0' Url = 'https://github.com/STMicroelectronics/STM32CubeF4/archive/refs/tags/v1.28.0.zip' Sha256 = 'TODO' # TODO(vendor): 실제 zip SHA-256 기입 Strip = $true }, @{ Name = 'FreeRTOS-Kernel' Tag = 'V11.1.0' Url = 'https://github.com/FreeRTOS/FreeRTOS-Kernel/archive/refs/tags/V11.1.0.zip' Sha256 = 'TODO' # TODO(vendor) Strip = $true }, @{ Name = 'lwip' Tag = 'STABLE-2_2_0_RELEASE' Url = 'https://github.com/lwip-tcpip/lwip/archive/refs/tags/STABLE-2_2_0_RELEASE.zip' Sha256 = 'TODO' # TODO(vendor) Strip = $true }, @{ Name = 'mbedtls' Tag = 'v3.6.2' Url = 'https://github.com/Mbed-TLS/mbedtls/releases/download/mbedtls-3.6.2/mbedtls-3.6.2.tar.bz2' Sha256 = 'TODO' # TODO(vendor): release tarball 은 공식 SHA-256 제공됨 Strip = $true } ) New-Item -ItemType Directory -Force -Path $ThirdParty | Out-Null if ($KeepArchives) { New-Item -ItemType Directory -Force -Path $ArchiveDir | Out-Null } function Get-FileSha256([string]$path) { (Get-FileHash -Algorithm SHA256 -Path $path).Hash.ToLowerInvariant() } # 압축 해제(zip / tar.bz2 / tar.gz). tar 는 Windows 10+ 내장 bsdtar 사용. function Expand-Archive2([string]$archive, [string]$dest) { New-Item -ItemType Directory -Force -Path $dest | Out-Null if ($archive -match '\.zip$') { Expand-Archive -Path $archive -DestinationPath $dest -Force } elseif ($archive -match '\.(tar\.bz2|tbz2|tar\.gz|tgz|tar\.xz)$') { if (-not (Get-Command 'tar' -ErrorAction SilentlyContinue)) { throw "tar 가 필요합니다(.tar.* 해제). Windows 10+ 내장 tar 또는 git tar 사용." } & tar -xf $archive -C $dest if ($LASTEXITCODE -ne 0) { throw "tar 해제 실패: $archive" } } else { throw "지원하지 않는 압축 형식: $archive" } } foreach ($dep in $Deps) { $name = $dep.Name $target = Join-Path $ThirdParty $name Write-Host "──────────────────────────────────────────────" -ForegroundColor Cyan Write-Host " 벤더링: $name ($($dep.Tag))" -ForegroundColor Cyan if ((Test-Path $target) -and -not $Force) { Write-Host " 이미 존재 — 건너뜀(재벤더링은 -Force). $target" -ForegroundColor Yellow continue } if (Test-Path $target) { Remove-Item -Recurse -Force $target } # ── 다운로드 ───────────────────────────────────────────────────────────── $tmp = New-Item -ItemType Directory -Force ` -Path (Join-Path ([IO.Path]::GetTempPath()) ("vendor_" + [Guid]::NewGuid())) $fileName = [IO.Path]::GetFileName(([Uri]$dep.Url).AbsolutePath) $archive = Join-Path $tmp $fileName Write-Host " 다운로드: $($dep.Url)" Invoke-WebRequest -Uri $dep.Url -OutFile $archive -UseBasicParsing # ── 무결성 검증(SHA-256) ──────────────────────────────────────────────── $actual = Get-FileSha256 $archive if ($dep.Sha256 -eq 'TODO' -or [string]::IsNullOrWhiteSpace($dep.Sha256)) { Write-Host " [경고] SHA-256 미설정 — 검증 생략. 계산값을 기록하세요:" -ForegroundColor Yellow Write-Host " $name : $actual" -ForegroundColor Yellow } elseif ($actual -ne $dep.Sha256.ToLowerInvariant()) { throw "SHA-256 불일치($name)`n 기대: $($dep.Sha256)`n 실제: $actual" } else { Write-Host " SHA-256 확인됨: $actual" -ForegroundColor Green } # ── 해제 + 최상위 폴더 벗기기 ──────────────────────────────────────────── $extract = Join-Path $tmp 'x' Expand-Archive2 $archive $extract if ($dep.Strip) { $top = @(Get-ChildItem -Directory $extract) if ($top.Count -eq 1) { Move-Item -Path $top[0].FullName -Destination $target } else { # 최상위가 단일 폴더가 아니면 그대로 이동. Move-Item -Path $extract -Destination $target } } else { Move-Item -Path $extract -Destination $target } if ($KeepArchives) { Copy-Item -Path $archive -Destination (Join-Path $ArchiveDir $fileName) -Force } Remove-Item -Recurse -Force $tmp -ErrorAction SilentlyContinue Write-Host " 완료 → $target" -ForegroundColor Green } Write-Host "──────────────────────────────────────────────" -ForegroundColor Cyan Write-Host " 벤더링 완료. 이제 폐쇄망 빌드 가능: pwsh firmware/scripts/build.ps1" -ForegroundColor Green Write-Host " TODO(vendor): 위 출력된 SHA-256 값을 \$Deps 에 채워 재현성을 고정하세요." -ForegroundColor Yellow