1- # Shared helpers for running work against the desired `core/` submodule commit.
1+ # Shared helper for reading sources from the desired `core/` submodule commit
2+ # WITHOUT mutating the submodule's working tree.
23#
3- # Dot-source this file to import Enter-CoreCommit / Restore-CoreCommit , then wrap
4- # the work in a try/finally:
4+ # Dot-source this file to import Get-CoreSourceRoot / Remove-CoreSourceRoot , then
5+ # wrap the work in a try/finally so the temporary extraction (if any) is cleaned up :
56#
67# . (Join-Path $packageRoot "CoreCommit.ps1")
7- # $originSha = Enter-CoreCommit -CoreRoot $coreRoot -PackageRoot $packageRoot
8+ # $core = Get-CoreSourceRoot -CoreRoot $coreRoot -PackageRoot $packageRoot
89# try {
9- # # copy/sync from the (possibly pinned) core checkout
10+ # # read sources from under $core.Root (e.g. emitter/, generator/)
1011# }
1112# finally {
12- # Restore-CoreCommit -CoreRoot $coreRoot -OriginSha $originSha
13+ # Remove-CoreSourceRoot $core
1314# }
1415#
1516# The desired core commit is pinned in <PackageRoot>/core-commit.json. When that
1617# file is present and its commit is newer than the submodule's current checkout,
17- # Enter-CoreCommit temporarily checks it out; Restore-CoreCommit puts the submodule
18- # back to its original SHA (repo-wide `pnpm build` runs these scripts and CI checks
19- # git status).
18+ # Get-CoreSourceRoot extracts that commit's `packages/http-client-java` subtree into
19+ # a temporary directory via `git archive`. It never checks out a different SHA or
20+ # otherwise touches the submodule's working tree, so it is safe to run while a
21+ # parallel monorepo build (`pnpm build`, `regen-all-packages-docs`, ...) reads the
22+ # core/ submodule concurrently, and `git status` on core always stays clean.
23+ #
24+ # When core-commit.json is absent, or its commit is not newer than the current
25+ # checkout, the sources are read straight from the submodule's current working tree.
26+
27+ # Path (relative to the core repo root) of the subtree the Java package reads from.
28+ $script :CoreJavaSubtree = " packages/http-client-java"
2029
21- # Check out the pinned core commit (when newer) and return the submodule's original
22- # SHA so the caller can restore it in a finally block.
23- function Enter-CoreCommit {
30+ # Resolve where to read the core Java sources from and return a descriptor:
31+ # @{ Root = <dir containing emitter/, generator/, ...>; TempDir = <temp dir or $null> }
32+ # Pass the descriptor to Remove-CoreSourceRoot in a finally block to clean up.
33+ function Get-CoreSourceRoot {
2434 param (
2535 # Path to the `core/` submodule.
2636 [Parameter (Mandatory = $true )]
@@ -31,59 +41,107 @@ function Enter-CoreCommit {
3141 [string ] $PackageRoot
3242 )
3343
34- # Capture the submodule's current SHA so the caller can restore it.
44+ # Never let any git invocation below block on an interactive credential or
45+ # host-key prompt. During a parallel `pnpm build` the emitter build runs
46+ # headless (turbo captures its stdio / attaches a pseudo-terminal), so a git
47+ # prompt has nowhere to read from and would hang the whole build forever --
48+ # observed as an intermittent, Java-only build hang. Make git fail fast instead.
49+ $env: GIT_TERMINAL_PROMPT = " 0"
50+
51+ # Silence progress: a cmdlet rendering Write-Progress (e.g. the archive
52+ # extraction below) touches the controlling terminal, and under turbo's
53+ # background process group that stops this process with SIGTTIN/SIGTTOU and
54+ # hangs the build. Scoped to this function so callers are unaffected.
55+ $ProgressPreference = " SilentlyContinue"
56+
3557 $originSha = (git - C $CoreRoot rev- parse HEAD).Trim()
3658 if ($LASTEXITCODE -ne 0 ) {
3759 Write-Error " Failed to read the current SHA of the 'core' submodule at: $CoreRoot `n Make sure it is checked out (git submodule update --init --recursive)."
3860 }
3961
62+ $liveRoot = Join-Path $CoreRoot $script :CoreJavaSubtree
63+
4064 # core-commit.json is optional: when present it pins the core commit to use;
4165 # when absent we just use the submodule's current checkout.
4266 $configPath = Join-Path $PackageRoot " core-commit.json"
43- $targetSha = $null
44- if (Test-Path $configPath ) {
45- $targetSha = ((Get-Content - Raw $configPath | ConvertFrom-Json ).sha).Trim()
46- if ([string ]::IsNullOrWhiteSpace($targetSha )) {
47- Write-Error " No 'sha' found in $configPath ."
48- }
49- }
50- else {
67+ if (-not (Test-Path $configPath )) {
5168 Write-Host " core-commit.json not found; using current core checkout $originSha ."
69+ return @ { Root = $liveRoot ; TempDir = $null }
70+ }
71+
72+ $targetSha = ((Get-Content - Raw $configPath | ConvertFrom-Json ).sha).Trim()
73+ if ([string ]::IsNullOrWhiteSpace($targetSha )) {
74+ Write-Error " No 'sha' found in $configPath ."
5275 }
5376
54- if ($targetSha -and $targetSha -ne $originSha ) {
55- # Make sure the target commit is available locally (it may be newer than
56- # what has been fetched); ignore failure if it is already present.
77+ if ($targetSha -eq $originSha ) {
78+ return @ { Root = $liveRoot ; TempDir = $null }
79+ }
80+
81+ # Make sure the target commit is available locally (the pin may be newer than
82+ # what has been fetched). Only hit the network when the commit is genuinely
83+ # missing: this code runs on EVERY emitter build and doc regen, and a `git
84+ # fetch` on core/ is the only per-build network operation in the whole repo
85+ # build, so doing it unconditionally turns any transient network/credential
86+ # stall into a Java-only build hang. `git cat-file -e <sha>^{commit}` is a
87+ # cheap, offline existence check.
88+ git - C $CoreRoot cat- file - e " ${targetSha} ^{commit}" 2> $null
89+ if ($LASTEXITCODE -ne 0 ) {
5790 git - C $CoreRoot fetch -- quiet origin $targetSha 2> $null
91+ }
5892
59- # Only move forward: check out the target when it is a descendant (newer)
60- # of the current checkout. `merge-base --is-ancestor A B` exits 0 when A is
61- # an ancestor of B, i.e. B is newer than A.
62- git - C $CoreRoot merge-base - -is - ancestor $originSha $targetSha
63- if ($LASTEXITCODE -eq 0 ) {
64- Write-Host " Checking out newer core commit $targetSha (was $originSha )"
65- git - C $CoreRoot checkout -- quiet $targetSha
66- if ($LASTEXITCODE -ne 0 ) {
67- Write-Error " Failed to checkout core commit $targetSha ."
68- }
69- }
70- else {
71- Write-Host " core-commit.json SHA $targetSha is not newer than current $originSha ; using current checkout."
72- }
93+ # Only move forward: use the target when it is a descendant (newer) of the
94+ # current checkout. `merge-base --is-ancestor A B` exits 0 when A is an ancestor
95+ # of B, i.e. B is newer than A.
96+ git - C $CoreRoot merge-base - -is - ancestor $originSha $targetSha
97+ if ($LASTEXITCODE -ne 0 ) {
98+ Write-Host " core-commit.json SHA $targetSha is not newer than current $originSha ; using current checkout."
99+ return @ { Root = $liveRoot ; TempDir = $null }
73100 }
74101
75- return $originSha
102+ # The target is newer: extract only its packages/http-client-java subtree into a
103+ # temp directory with `git archive`. This never modifies the submodule's working
104+ # tree, so it stays safe under a parallel monorepo build that reads core/.
105+ Write-Host " Reading core sources from pinned commit $targetSha (was $originSha ) without checking it out"
106+ $tempDir = Join-Path ([System.IO.Path ]::GetTempPath()) (" typespec-java-core-" + [System.Guid ]::NewGuid().ToString(" N" ))
107+ New-Item - ItemType Directory - Path $tempDir | Out-Null
108+ $zipPath = Join-Path $tempDir " core.zip"
109+ $extractDir = Join-Path $tempDir " src"
110+ try {
111+ # Produce a zip (not tar) and expand it with .NET's ZipFile.ExtractToDirectory:
112+ # this keeps the whole flow inside PowerShell/.NET and avoids depending on an
113+ # external `tar`, whose behavior varies by platform (notably Git for Windows'
114+ # GNU tar treats a drive-letter path like C:\...\core.tar as a remote host).
115+ #
116+ # ExtractToDirectory is used instead of Expand-Archive on purpose: the archived
117+ # subtree holds several thousand files, and Expand-Archive emits a per-entry
118+ # Write-Progress that makes it pathologically slow on that many files (worse when
119+ # its output is captured by a parallel `pnpm build`), to the point the emitter
120+ # build looks hung. ZipFile.ExtractToDirectory has no such overhead.
121+ git - C $CoreRoot archive -- format= zip - o $zipPath " ${targetSha} :$script :CoreJavaSubtree "
122+ if ($LASTEXITCODE -ne 0 ) {
123+ Write-Error " Failed to archive core commit ${targetSha} :$script :CoreJavaSubtree ."
124+ }
125+ Add-Type - AssemblyName System.IO.Compression.FileSystem
126+ [System.IO.Compression.ZipFile ]::ExtractToDirectory($zipPath , $extractDir )
127+ }
128+ catch {
129+ Remove-Item $tempDir - Recurse - Force - ErrorAction SilentlyContinue
130+ throw
131+ }
132+ Remove-Item $zipPath - Force
133+ return @ { Root = $extractDir ; TempDir = $tempDir }
76134}
77135
78- # Restore the submodule to the SHA captured by Enter-CoreCommit.
79- function Restore-CoreCommit {
136+ # Clean up the temporary extraction created by Get-CoreSourceRoot (no-op when the
137+ # submodule's current checkout was read directly).
138+ function Remove-CoreSourceRoot {
80139 param (
81140 [Parameter (Mandatory = $true )]
82- [string ] $CoreRoot ,
83-
84- [Parameter (Mandatory = $true )]
85- [string ] $OriginSha
141+ $CoreSource
86142 )
87143
88- git - C $CoreRoot checkout -- quiet $OriginSha
144+ if ($CoreSource.TempDir ) {
145+ Remove-Item $CoreSource.TempDir - Recurse - Force - ErrorAction SilentlyContinue
146+ }
89147}
0 commit comments