Beispiel #1
0
func triggerDependentPackages(importName string) {
	// Note: This is currently over-cautious, matching even packages that currently import a different, vendored/unvendored version of this package
	// alog.Printf("@(dim:Triggering check of packages that import %s)\n", p.ImportName)
	for _, pkg := range packages {
		if pkg.Imports != nil && pkg.Imports.Has(importName) {
			switch pkg.State {
			case PackageReady, PackageDirtyIdle:
				if pkg.WasUpdated {
					queueBuild(pkg)
				} else {
					queueUpdate(pkg)
				}
			case PackageUpdateQueued, PackageUpdatingButDirty, PackageBuildingButDirty:
				// Package is already queued for update (or will be), so no need for change
			case PackageUpdating:
				chState(pkg, PackageUpdatingButDirty)
			case PackageBuilding:
				chState(pkg, PackageBuildingButDirty)
			case PackageBuildQueued:
				// Package already queued for build, so no need to change
			default:
				alog.Panicf("triggerDependentPackages saw unexpected state %s", pkg.State)
			}
		}
	}
}
Beispiel #2
0
func queueBuild(p *Package) {
	if enableSanityChecks {
		for _, pkg := range buildQueue {
			if pkg == p {
				alog.Panicf("Package %s already in buildQueue, cannot queue twice", p.Name)
			}
		}
	}
	chState(p, PackageBuildQueued)
	buildQueue = append(buildQueue, p)
}
Beispiel #3
0
func queueUpdate(p *Package) {
	if enableSanityChecks {
		for _, pkg := range updateQueue {
			if pkg == p {
				alog.Panicf("Package %s already in updateQueue, cannot queue twice", p.Name)
			}
		}
	}
	chState(p, PackageUpdateQueued)
	updateQueue = append(updateQueue, p)
}
Beispiel #4
0
func diagnoseNotReady(parent *Package, p *Package) {
	switch p.State {
	case PackageBuilding, PackageBuildingButDirty, PackageUpdating, PackageUpdatingButDirty, PackageUpdateQueued, PackageBuildQueued:
		// Just wait. Though it may be helpful to inform the user of this?
	case PackageDirtyIdle:
		alog.Printf("@(dim:Can't build) %s @(dim:because) %s @(dim:isn't ready.)\n", parent.Name, p.Name)
		p.LastBuildInputsModTime = time.Time{} // Force a build even if a previous one failed, so that we can get the output again
		queueUpdate(p)
	default:
		alog.Panicf("diagnoseNotReady encountered unexpected state %s", p.State)
	}
}
Beispiel #5
0
func unqueueBuild(p *Package) {
	found := false
	// alog.Printf("Unqueue %s\n", p.Name)
	// for _, pkg := range buildQueue {
	// 	alog.Printf("    @(dim:-- %s)\n", pkg.Name)
	// }
	for i, pkg := range buildQueue {
		if p == pkg {
			buildQueue = append(buildQueue[:i], buildQueue[i+1:]...)
			found = true
			break
		}
	}
	if !found {
		alog.Panicf("Package %s was in state PackageBuildQueued but could not be found in buildQueue.", p.Name)
	}
	chState(p, PackageDirtyIdle)
}
Beispiel #6
0
func getTimeout(state DispatchState) <-chan time.Time {
	switch state {
	case DispatchIdle:
		return neverChan
	case DispatchCanPushWork:
		// Debounce filesystem events a little (though `watcher` already should be doing that more aggressively).
		// In particular, this delay ensures that we process all messages waiting in the various queues before
		// pushing work to workers.
		return time.After(2 * time.Millisecond)
	case DispatchWaitingForWork:
		// Periodically output messages about outstanding builds, both to inform the user about progress on very
		// slow builds as well as to help debug "stuck" builds.
		return time.After(30 * time.Second)
	case DispatchMaybeFinishedInitialPass:
		// When we can wait a whole second with empty queues and no active work, then we call the initial pass
		// complete. This is kind of kludgy but is functional; a more "correct" solution would require `watcher`
		// informing us when *it* had completed a first full walk of the directory tree.
		return time.After(1 * time.Second)
	}
	alog.Panicf("getTimeout received unexpected DispatchState %s", state)
	return nil
}
Beispiel #7
0
func pushWork() {
	for numWorkersActive() < Opts.MaxWorkers && len(updateQueue) > 0 {
		var pkg *Package
		pkg, updateQueue = updateQueue[0], updateQueue[1:]
		if pkg.State != PackageUpdateQueued {
			alog.Panicf("Package %s was in updateQueue but had state %s", pkg.Name, pkg.State)
		}
		chState(pkg, PackageUpdating)
		numUpdatesActive++
		go update(pkg.Name)
	}
	for numWorkersActive() < Opts.MaxWorkers && len(buildQueue) > 0 {
		var pkg *Package
		pkg, buildQueue = buildQueue[0], buildQueue[1:]
		if pkg.State != PackageBuildQueued {
			alog.Panicf("Package %s was in buildQueue but had state %s", pkg.Name, pkg.State)
		}
		if !pkg.shouldBuild() {
			chState(pkg, PackageDirtyIdle)
		} else {
			recentDep, err := getMostRecentDep(pkg)
			if err == dependenciesNotReadyError {
				// At least one dependency is not ready
				chState(pkg, PackageDirtyIdle)
			} else if err != nil {
				alog.Panicf("@(error:Encountered unexpected error received from calcDepsModTime: %v)", err)
			} else {
				var inputsModTime time.Time
				if recentDep != nil {
					inputsModTime = recentDep.BuiltModTime
				}
				if pkg.SourceModTime.After(inputsModTime) {
					inputsModTime = pkg.SourceModTime
				}
				printTimes := func() {
					alog.Printf("    @(dim:Target ModTime) @(time:%s)\n", pkg.BuiltModTime.Format(DATE_FORMAT))
					if recentDep != nil {
						alog.Printf("    @(dim:  deps ModTime) @(time:%s) %s\n", recentDep.BuiltModTime.Format(DATE_FORMAT), recentDep.Name)
					} else {
						alog.Printf("    @(dim:  deps ModTime) n/a @(dim:no dependencies)\n")
					}
					alog.Printf("    @(dim:   src ModTime) @(time:%s) %s\n", pkg.SourceModTime.Format(DATE_FORMAT), pkg.RecentSrcName)
				}
				if !pkg.UpdateStartTime.After(inputsModTime) {
					// This package last updated after some of its inputs. Send it back to update again.
					queueUpdate(pkg)
				} else if !pkg.BuiltModTime.IsZero() && !inputsModTime.After(pkg.BuiltModTime) {
					// No need to build, as this package is already up to date.
					if Opts.Verbose {
						alog.Printf("@(dim:No need to build) %s\n", pkg.Name)
						printTimes()
					}
					chState(pkg, PackageReady)
					triggerDependentPackages(pkg.ImportName)
				} else if !inputsModTime.After(pkg.LastBuildInputsModTime) {
					if Opts.Verbose {
						// Sometimes, package updates/builds can be unnecessary triggered repeatedly. For example, sometimes builds themselves
						// can cause files to be touched in depended-upon packages, resulting in a cycle of endless failed builds (successful
						// builds would not be retried already because we would compare the timestamps and determine that the target was up to
						// date).
						alog.Printf("@(dim:Not building) %s@(dim:, as the package and its dependencies have not changed since its last build, which failed.)\n", pkg.Name)
					}
					chState(pkg, PackageDirtyIdle)
				} else {
					if Opts.Verbose && !pkg.BuiltModTime.IsZero() {
						alog.Printf("@(dim:Building) %s@(dim::)\n", pkg.Name)
						printTimes()
					}
					chState(pkg, PackageBuilding)
					numBuildsActive++
					pkg.LastBuildInputsModTime = inputsModTime
					go pkg.build()
				}
			}
		}
	}
}
Beispiel #8
0
func dispatcher() {
	startupLogger = alog.New(os.Stderr, "@(dim:{isodate}) ", 0)

	for {
		if !finishedInitialPass {
			updateStartupText(false)
		}
		dispatchState := getDispatchState()
		timeout := getTimeout(dispatchState)

		select {
		case p := <-buildSuccess:
			numBuildSucesses++
			numBuildsActive--
			switch p.State {
			case PackageBuilding:
				chState(p, PackageReady)
				p.BuiltModTime = p.UpdateStartTime
				p.LastBuildInputsModTime = time.Time{}
				triggerDependentPackages(p.ImportName)
			case PackageBuildingButDirty:
				queueUpdate(p)
			default:
				alog.Panicf("buildSuccess with state %s", p.State)
			}
		case p := <-buildFailure:
			numBuildFailures++
			numBuildsActive--
			switch p.State {
			case PackageBuilding:
				chState(p, PackageDirtyIdle)
			case PackageBuildingButDirty:
				queueUpdate(p)
			default:
				alog.Panicf("buildFailure with state %s", p.State)
			}
		case pUpdate := <-updateFinished:
			numUpdatesActive--
			p := packages[pUpdate.Name]
			if p == nil {
				alog.Panicf("Couldn't find package %s, yet dispatcher received an update for it.", pUpdate.Name)
			}
			switch p.State {
			case PackageUpdating:
				if pUpdate.UpdateError != nil {
					if pUpdate.RemovePackage {
						alog.Printf("@(dim:Removing package %s from index, as it has been removed from the filesystem.)\n", pUpdate.Name)
						delete(packages, p.Name)
						// Trigger updates of any packages that depend on this import name
						// XXX this should be modified if triggerDependentPackages is made more specific in the future
						triggerDependentPackages(p.ImportName)
					} else {
						chState(p, PackageDirtyIdle)
					}
				} else {
					p.mergeUpdate(pUpdate)
					queueBuild(p)
				}
			case PackageUpdatingButDirty:
				queueUpdate(p)
			default:
				alog.Panicf("updateFinished with state %s", p.State)
			}
		case pName := <-moduleUpdateChan:
			p := packages[pName]
			if p == nil {
				p = NewPackage(pName)
				p.init()
				packages[pName] = p
				numUnready++
			}
			switch p.State {
			case PackageReady, PackageDirtyIdle:
				queueUpdate(p)
			case PackageUpdating:
				chState(p, PackageUpdatingButDirty)
			case PackageBuilding:
				chState(p, PackageBuildingButDirty)
			case PackageUpdateQueued, PackageUpdatingButDirty, PackageBuildingButDirty:
				// Already have an update queued (or will), no need to change
			case PackageBuildQueued:
				// Has a build queued, but we need to do update first. Splice it out of the buildQueue, then queue the update.
				unqueueBuild(p)
				queueUpdate(p)
			default:
				alog.Panicf("moduleUpdateChan encountered unexpected state %s", p.State)
			}
		case <-timeout:
			switch dispatchState {
			case DispatchMaybeFinishedInitialPass:
				// We've reached the conclusion of the initial pass
				finishedInitialPass = true
				printStartupSummary()
			case DispatchCanPushWork:
				pushWork()
			case DispatchWaitingForWork:
				for _, p := range packages {
					switch p.State {
					case PackageBuilding, PackageBuildingButDirty:
						alog.Printf("@(dim:Still building %s...)\n", p.Name)
					case PackageUpdating, PackageUpdatingButDirty:
						alog.Printf("@(dim:Still checking %s...)\n", p.Name)
					}
				}
			default:
				alog.Panicf("dispatch hit timeout with unexpected dispatchState %s", dispatchState)
			}
		}
	}
}