Skip to content

Commit 042a471

Browse files
Use package.json locations for build up-to-date checks (#4301)
Co-authored-by: Sheetal Nandi <shkamat@microsoft.com>
1 parent f7acbad commit 042a471

80 files changed

Lines changed: 3797 additions & 205 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

internal/compiler/program.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,11 @@ func (p *Program) GetPackageJsonInfo(pkgJsonPath string) *packagejson.InfoCacheE
152152
return nil
153153
}
154154

155+
// PackageJsonCacheEntries iterates on all package json cache entries.
156+
func (p *Program) PackageJsonCacheEntries(f func(key tspath.Path, value *packagejson.InfoCacheEntry) bool) {
157+
p.resolver.PackageJsonCacheEntries(f)
158+
}
159+
155160
// GetRedirectTargets returns the list of file paths that redirect to the given path.
156161
// These are files from the same package (same name@version) installed in different locations.
157162
func (p *Program) GetRedirectTargets(path tspath.Path) []string {

internal/execute/build/buildtask.go

Lines changed: 69 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package build
22

33
import (
44
"fmt"
5+
"slices"
56
"strings"
67
"sync"
78
"sync/atomic"
@@ -63,6 +64,7 @@ type BuildTask struct {
6364

6465
buildInfoEntry *buildInfoEntry
6566
buildInfoEntryMu sync.Mutex
67+
packageJsons []string
6668

6769
errors []*ast.Diagnostic
6870
pending atomic.Bool
@@ -231,6 +233,7 @@ func (t *BuildTask) compileAndEmit(orchestrator *Orchestrator, path tspath.Path)
231233
})
232234
t.result.exitStatus = result.Status
233235
t.result.statistics = statistics
236+
t.packageJsons = t.result.program.PackageJsonLookupPaths()
234237
if (!program.Options().NoEmitOnError.IsTrue() || len(result.Diagnostics) == 0) &&
235238
(len(result.EmitResult.EmittedFiles) > 0 || t.status.kind != upToDateStatusTypeOutOfDateBuildInfoWithErrors) {
236239
// Update time stamps for rest of the outputs
@@ -327,6 +330,9 @@ func (t *BuildTask) getUpToDateStatus(orchestrator *Orchestrator, configPath tsp
327330

328331
// Check the build info
329332
buildInfoPath := t.resolved.GetBuildInfoFileName()
333+
getBuildInfoDirectory := core.Memoize(func() string {
334+
return tspath.GetDirectoryPath(tspath.GetNormalizedAbsolutePath(buildInfoPath, orchestrator.comparePathsOptions.CurrentDirectory))
335+
})
330336
buildInfo, buildInfoTime := t.loadOrStoreBuildInfo(orchestrator, configPath, buildInfoPath)
331337
if buildInfo == nil {
332338
return &upToDateStatus{kind: upToDateStatusTypeOutputMissing, data: buildInfoPath}
@@ -363,15 +369,17 @@ func (t *BuildTask) getUpToDateStatus(orchestrator *Orchestrator, configPath tsp
363369
}
364370

365371
// Some of the emit files like source map or dts etc are not yet done
366-
if buildInfo.IsEmitPending(t.resolved, tspath.GetDirectoryPath(tspath.GetNormalizedAbsolutePath(buildInfoPath, orchestrator.comparePathsOptions.CurrentDirectory))) {
372+
if buildInfo.IsEmitPending(t.resolved, getBuildInfoDirectory()) {
367373
return &upToDateStatus{kind: upToDateStatusTypeOutOfDateOptions, data: buildInfoPath}
368374
}
369375
}
370376
var inputTextUnchanged bool
371377
oldestOutputFileAndTime := fileAndTime{buildInfoPath, buildInfoTime}
372378
var newestInputFileAndTime fileAndTime
373379
var seenRoots collections.Set[tspath.Path]
374-
var buildInfoRootInfoReader *incremental.BuildInfoRootInfoReader
380+
getBuildInfoRootInfoReader := core.Memoize(func() *incremental.BuildInfoRootInfoReader {
381+
return buildInfo.GetBuildInfoRootInfoReader(getBuildInfoDirectory(), orchestrator.comparePathsOptions)
382+
})
375383
for _, inputFile := range t.resolved.FileNames() {
376384
inputTime := orchestrator.host.GetMTime(inputFile)
377385
if inputTime.IsZero() {
@@ -382,10 +390,7 @@ func (t *BuildTask) getUpToDateStatus(orchestrator *Orchestrator, configPath tsp
382390
var version string
383391
var currentVersion string
384392
if buildInfo.IsIncremental() {
385-
if buildInfoRootInfoReader == nil {
386-
buildInfoRootInfoReader = buildInfo.GetBuildInfoRootInfoReader(tspath.GetDirectoryPath(tspath.GetNormalizedAbsolutePath(buildInfoPath, orchestrator.comparePathsOptions.CurrentDirectory)), orchestrator.comparePathsOptions)
387-
}
388-
buildInfoFileInfo, resolvedInputPath := buildInfoRootInfoReader.GetBuildInfoFileInfo(inputPath)
393+
buildInfoFileInfo, resolvedInputPath := getBuildInfoRootInfoReader().GetBuildInfoFileInfo(inputPath)
389394
if fileInfo := buildInfoFileInfo.GetFileInfo(); fileInfo != nil && fileInfo.Version() != "" {
390395
version = fileInfo.Version()
391396
if text, ok := orchestrator.host.FS().ReadFile(string(resolvedInputPath)); ok {
@@ -407,16 +412,54 @@ func (t *BuildTask) getUpToDateStatus(orchestrator *Orchestrator, configPath tsp
407412
seenRoots.Add(inputPath)
408413
}
409414

410-
if buildInfoRootInfoReader == nil {
411-
buildInfoRootInfoReader = buildInfo.GetBuildInfoRootInfoReader(tspath.GetDirectoryPath(tspath.GetNormalizedAbsolutePath(buildInfoPath, orchestrator.comparePathsOptions.CurrentDirectory)), orchestrator.comparePathsOptions)
412-
}
413-
for root := range buildInfoRootInfoReader.Roots() {
415+
for root := range getBuildInfoRootInfoReader().Roots() {
414416
if !seenRoots.Has(root) {
415417
// File was root file when project was built but its not any more
416418
return &upToDateStatus{kind: upToDateStatusTypeOutOfDateRoots, data: &inputOutputName{string(root), buildInfoPath}}
417419
}
418420
}
419421

422+
if buildInfo.IsIncremental() {
423+
var resolvedRoots collections.Set[tspath.Path]
424+
for root := range getBuildInfoRootInfoReader().Roots() {
425+
if _, resolved := getBuildInfoRootInfoReader().GetBuildInfoFileInfo(root); resolved != "" {
426+
resolvedRoots.Add(resolved)
427+
}
428+
}
429+
for index, buildInfoFileInfo := range buildInfo.FileInfos {
430+
buildInfoFileName := buildInfo.FileNames[index]
431+
// Lib files bundled with the compiler can change only with the version of the compiler,
432+
// which is already verified with buildInfo.Version
433+
if incremental.IsBuildInfoFileNameDefaultLibrary(buildInfoFileName) {
434+
continue
435+
}
436+
inputFile := tspath.GetNormalizedAbsolutePath(buildInfoFileName, getBuildInfoDirectory())
437+
inputPath := orchestrator.toPath(inputFile)
438+
// Root files are already checked
439+
if seenRoots.Has(inputPath) || resolvedRoots.Has(inputPath) {
440+
continue
441+
}
442+
inputTime := orchestrator.host.GetMTime(inputFile)
443+
if inputTime.IsZero() {
444+
// Input file that was part of the program is missing (eg: dependency was removed)
445+
return &upToDateStatus{kind: upToDateStatusTypeInputFileMissing, data: inputFile}
446+
}
447+
if inputTime.After(oldestOutputFileAndTime.time) {
448+
var currentVersion string
449+
version := buildInfoFileInfo.GetFileInfo().Version()
450+
if version != "" {
451+
if text, ok := orchestrator.host.FS().ReadFile(inputFile); ok {
452+
currentVersion = incremental.ComputeHash(text, orchestrator.opts.Testing != nil)
453+
}
454+
}
455+
if version == "" || version != currentVersion {
456+
return &upToDateStatus{kind: upToDateStatusTypeInputFileNewer, data: &inputOutputName{inputFile, buildInfoPath}}
457+
}
458+
inputTextUnchanged = true
459+
}
460+
}
461+
}
462+
420463
if !t.resolved.CompilerOptions().IsIncremental() {
421464
// Check output file stamps
422465
for outputFile := range t.resolved.GetOutputFileNames() {
@@ -493,14 +536,22 @@ func (t *BuildTask) getUpToDateStatus(orchestrator *Orchestrator, configPath tsp
493536
}
494537
}
495538

496-
// !!! sheetal TODO : watch??
497-
// // Check package file time
498-
// const packageJsonLookups = state.lastCachedPackageJsonLookups.get(resolvedPath);
499-
// const dependentPackageFileStatus = packageJsonLookups && forEachKey(
500-
// packageJsonLookups,
501-
// path => checkConfigFileUpToDateStatus(state, path, oldestOutputFileTime, oldestOutputFileName),
502-
// );
503-
// if (dependentPackageFileStatus) return dependentPackageFileStatus;
539+
for packageJson := range buildInfo.GetPackageJsons(getBuildInfoDirectory()) {
540+
packageJsonTime := orchestrator.host.GetMTime(packageJson)
541+
if packageJsonTime.IsZero() {
542+
return &upToDateStatus{kind: upToDateStatusTypeInputFileMissing, data: packageJson}
543+
}
544+
if packageJsonTime.After(oldestOutputFileAndTime.time) {
545+
return &upToDateStatus{kind: upToDateStatusTypeInputFileNewer, data: &inputOutputName{packageJson, oldestOutputFileAndTime.file}}
546+
}
547+
}
548+
for packageJson := range buildInfo.GetMissingPackageJsons(getBuildInfoDirectory()) {
549+
if !orchestrator.host.GetMTime(packageJson).IsZero() {
550+
return &upToDateStatus{kind: upToDateStatusTypeInputFileNewer, data: &inputOutputName{packageJson, oldestOutputFileAndTime.file}}
551+
}
552+
}
553+
t.packageJsons = slices.Collect(buildInfo.GetPackageJsons(getBuildInfoDirectory()))
554+
t.packageJsons = append(t.packageJsons, slices.Collect(buildInfo.GetMissingPackageJsons(getBuildInfoDirectory()))...)
504555

505556
return &upToDateStatus{
506557
kind: core.IfElse(

internal/execute/build/orchestrator.go

Lines changed: 84 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"github.com/microsoft/typescript-go/internal/compiler"
1515
"github.com/microsoft/typescript-go/internal/core"
1616
"github.com/microsoft/typescript-go/internal/diagnostics"
17+
"github.com/microsoft/typescript-go/internal/execute/incremental"
1718
"github.com/microsoft/typescript-go/internal/execute/tsc"
1819
"github.com/microsoft/typescript-go/internal/execute/watchmanager"
1920
"github.com/microsoft/typescript-go/internal/fswatch"
@@ -86,7 +87,7 @@ func (o *Orchestrator) toPath(fileName string) tspath.Path {
8687
}
8788

8889
func (o *Orchestrator) resolveBuildInfoFileName(fileName string, buildInfoDir string) string {
89-
if !strings.HasPrefix(fileName, ".") {
90+
if incremental.IsBuildInfoFileNameDefaultLibrary(fileName) {
9091
return tspath.CombinePaths(o.host.DefaultLibraryPath(), fileName)
9192
}
9293
return tspath.GetNormalizedAbsolutePath(fileName, buildInfoDir)
@@ -279,9 +280,9 @@ func (o *Orchestrator) resetCaches() {
279280
}
280281

281282
func (o *Orchestrator) checkTasksForEventChanges(changedPaths map[string]fswatch.EventKind, needsConfigUpdate, needsUpdate *atomic.Bool) {
282-
normalizedPaths := make(map[tspath.Path]struct{}, len(changedPaths))
283-
for eventPath := range changedPaths {
284-
normalizedPaths[o.toPath(eventPath)] = struct{}{}
283+
normalizedPaths := make(map[tspath.Path]fswatch.EventKind, len(changedPaths))
284+
for eventPath, kind := range changedPaths {
285+
normalizedPaths[o.toPath(eventPath)] = kind
285286
}
286287

287288
for i := range o.order {
@@ -348,6 +349,27 @@ func (o *Orchestrator) checkTasksForEventChanges(changedPaths map[string]fswatch
348349
break
349350
}
350351
}
352+
for packageJson := range bi.buildInfo.GetPackageJsons(buildInfoDir) {
353+
if o.packageJsonLookupChanged(packageJson, normalizedPaths) {
354+
task.resetStatus()
355+
needsUpdate.Store(true)
356+
break
357+
}
358+
}
359+
for packageJson := range bi.buildInfo.GetMissingPackageJsons(buildInfoDir) {
360+
if o.packageJsonLookupChanged(packageJson, normalizedPaths) {
361+
task.resetStatus()
362+
needsUpdate.Store(true)
363+
break
364+
}
365+
}
366+
}
367+
for _, packageJson := range task.packageJsons {
368+
if o.packageJsonLookupChanged(packageJson, normalizedPaths) {
369+
task.resetStatus()
370+
needsUpdate.Store(true)
371+
break
372+
}
351373
}
352374
}
353375

@@ -381,6 +403,19 @@ func (o *Orchestrator) checkTasksForEventChanges(changedPaths map[string]fswatch
381403
}
382404
}
383405

406+
func (o *Orchestrator) packageJsonLookupChanged(packageJson string, changedPaths map[tspath.Path]fswatch.EventKind) bool {
407+
packageJsonPath := o.toPath(packageJson)
408+
if _, changed := changedPaths[packageJsonPath]; changed {
409+
return true
410+
}
411+
for changedPath, kind := range changedPaths {
412+
if kind == fswatch.EventDelete && tspath.ContainsPath(string(changedPath), string(packageJsonPath), o.comparePathsOptions) {
413+
return true
414+
}
415+
}
416+
return false
417+
}
418+
384419
func (o *Orchestrator) computeDesiredWatches() map[string]bool {
385420
desiredDirs := make(map[string]bool)
386421

@@ -438,7 +473,7 @@ func (o *Orchestrator) computeDesiredWatches() map[string]bool {
438473
buildInfoDir := tspath.GetDirectoryPath(string(bi.path))
439474
roots := collections.NewSetFromItems(core.Map(task.resolved.FileNames(), o.toPath)...)
440475
for _, fileName := range bi.buildInfo.FileNames {
441-
absPath := o.resolveBuildInfoFileName(fileName, buildInfoDir)
476+
absPath := o.host.FS().Realpath(o.resolveBuildInfoFileName(fileName, buildInfoDir))
442477
fp := o.toPath(absPath)
443478
if roots.Has(fp) {
444479
continue
@@ -450,12 +485,56 @@ func (o *Orchestrator) computeDesiredWatches() map[string]bool {
450485
}
451486
}
452487
}
488+
for packageJson := range bi.buildInfo.GetPackageJsons(buildInfoDir) {
489+
o.addPackageJsonWatchDirs(desiredDirs, packageJson)
490+
}
491+
for packageJson := range bi.buildInfo.GetMissingPackageJsons(buildInfoDir) {
492+
o.addPackageJsonWatchDirs(desiredDirs, packageJson)
493+
}
494+
}
495+
for _, packageJson := range task.packageJsons {
496+
o.addPackageJsonWatchDirs(desiredDirs, packageJson)
453497
}
454498
}
455499

456500
return o.wm.ResolveDesiredDirs(desiredDirs)
457501
}
458502

503+
func (o *Orchestrator) addWatchDir(desiredDirs map[string]bool, dir string) {
504+
if !watchmanager.IsDirCoveredByWatch(desiredDirs, dir, o.comparePathsOptions) && watchmanager.CanWatchDirectory(dir) {
505+
desiredDirs[dir] = false
506+
}
507+
}
508+
509+
func (o *Orchestrator) addPackageJsonWatchDirs(desiredDirs map[string]bool, packageJson string) {
510+
dir := tspath.GetDirectoryPath(packageJson)
511+
dirs := []string{dir}
512+
foundNodeModules := false
513+
for current := dir; ; {
514+
parent := tspath.GetDirectoryPath(current)
515+
if parent == "" || parent == current {
516+
break
517+
}
518+
dirs = append(dirs, parent)
519+
if tspath.GetBaseFileName(parent) == "node_modules" {
520+
foundNodeModules = true
521+
if grandparent := tspath.GetDirectoryPath(parent); grandparent != "" && grandparent != parent {
522+
dirs = append(dirs, grandparent)
523+
}
524+
break
525+
}
526+
current = parent
527+
}
528+
529+
if !foundNodeModules {
530+
o.addWatchDir(desiredDirs, dir)
531+
return
532+
}
533+
for _, dir := range dirs {
534+
o.addWatchDir(desiredDirs, dir)
535+
}
536+
}
537+
459538
func (o *Orchestrator) DoCycle() {
460539
o.wm.Lock()
461540
defer o.wm.Unlock()

internal/execute/incremental/buildInfo.go

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -463,9 +463,11 @@ type BuildInfo struct {
463463
Version string `json:"version,omitzero"`
464464

465465
// Common between incremental and tsc -b buildinfo for non incremental programs
466-
Errors bool `json:"errors,omitzero"`
467-
CheckPending bool `json:"checkPending,omitzero"`
468-
Root []*BuildInfoRoot `json:"root,omitzero"`
466+
Errors bool `json:"errors,omitzero"`
467+
CheckPending bool `json:"checkPending,omitzero"`
468+
Root []*BuildInfoRoot `json:"root,omitzero"`
469+
PackageJsons []string `json:"packageJsons,omitzero"`
470+
MissingPackageJsons []string `json:"missingPackageJsons,omitzero"`
469471

470472
// IncrementalProgram info
471473
FileNames []string `json:"fileNames,omitzero"`
@@ -493,11 +495,21 @@ func (b *BuildInfo) IsIncremental() bool {
493495
return b != nil && len(b.FileNames) != 0
494496
}
495497

498+
func IsBuildInfoFileNameDefaultLibrary(fileName string) bool {
499+
return !tspath.PathIsRelative(fileName) && !tspath.PathIsAbsolute(fileName)
500+
}
501+
496502
func (b *BuildInfo) fileName(fileId BuildInfoFileId) string {
503+
if fileId < 1 || int(fileId) > len(b.FileNames) {
504+
return ""
505+
}
497506
return b.FileNames[fileId-1]
498507
}
499508

500509
func (b *BuildInfo) fileInfo(fileId BuildInfoFileId) *BuildInfoFileInfo {
510+
if fileId < 1 || int(fileId) > len(b.FileInfos) {
511+
return nil
512+
}
501513
return b.FileInfos[fileId-1]
502514
}
503515

@@ -529,6 +541,24 @@ func (b *BuildInfo) IsEmitPending(resolved *tsoptions.ParsedCommandLine, buildIn
529541
return false
530542
}
531543

544+
func (b *BuildInfo) GetPackageJsons(buildInfoDirectory string) iter.Seq[string] {
545+
return getNormalizedPaths(b.PackageJsons, buildInfoDirectory)
546+
}
547+
548+
func (b *BuildInfo) GetMissingPackageJsons(buildInfoDirectory string) iter.Seq[string] {
549+
return getNormalizedPaths(b.MissingPackageJsons, buildInfoDirectory)
550+
}
551+
552+
func getNormalizedPaths(paths []string, buildInfoDirectory string) iter.Seq[string] {
553+
return func(yield func(string) bool) {
554+
for _, path := range paths {
555+
if !yield(tspath.GetNormalizedAbsolutePath(path, buildInfoDirectory)) {
556+
return
557+
}
558+
}
559+
}
560+
}
561+
532562
func (b *BuildInfo) GetBuildInfoRootInfoReader(buildInfoDirectory string, comparePathOptions tspath.ComparePathsOptions) *BuildInfoRootInfoReader {
533563
resolvedRootFileInfos := make(map[tspath.Path]*BuildInfoFileInfo, len(b.FileNames))
534564
// Roots of the File
@@ -540,10 +570,17 @@ func (b *BuildInfo) GetBuildInfoRootInfoReader(buildInfoDirectory string, compar
540570

541571
// Create map from resolvedRoot to Root
542572
for _, resolved := range b.ResolvedRoot {
543-
resolvedToRoot[toPath(b.fileName(resolved.Resolved))] = toPath(b.fileName(resolved.Root))
573+
resolvedRoot := b.fileName(resolved.Resolved)
574+
root := b.fileName(resolved.Root)
575+
if resolvedRoot != "" && root != "" {
576+
resolvedToRoot[toPath(resolvedRoot)] = toPath(root)
577+
}
544578
}
545579

546580
addRoot := func(resolvedRoot string, fileInfo *BuildInfoFileInfo) {
581+
if resolvedRoot == "" {
582+
return
583+
}
547584
resolvedRootPath := toPath(resolvedRoot)
548585
if rootPath, ok := resolvedToRoot[resolvedRootPath]; ok {
549586
rootToResolved.Set(rootPath, resolvedRootPath)

0 commit comments

Comments
 (0)