Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions internal/core/buildoptions.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ package core
type BuildOptions struct {
_ noCopy

Dry Tristate `json:"dry,omitzero"`
Force Tristate `json:"force,omitzero"`
Verbose Tristate `json:"verbose,omitzero"`
StopBuildOnErrors Tristate `json:"stopBuildOnErrors,omitzero"`
Dry Tristate `json:"dry,omitzero"`
Force Tristate `json:"force,omitzero"`
Verbose Tristate `json:"verbose,omitzero"`
MaxConcurrentProjects *int `json:"maxConcurrentProjects,omitzero"`
StopBuildOnErrors Tristate `json:"stopBuildOnErrors,omitzero"`

// CompilerOptions are not parsed here and will be available on ParsedBuildCommandLine

Expand Down
4 changes: 4 additions & 0 deletions internal/diagnostics/diagnostics_generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions internal/diagnostics/extraDiagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,13 @@
"Project '{0}' is out of date because it has errors.": {
"category": "Message",
"code": 6423
},
"Option '{0}' requires value to be greater than 0.": {
"category": "Error",
"code": 5002
},
"Specify the maximum number of projects that can be built concurrently.": {
"category": "Message",
"code": 6730
}
}
2 changes: 2 additions & 0 deletions internal/execute/build/buildtask.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,8 @@ func (t *buildTask) updateDownstream(orchestrator *Orchestrator, path tspath.Pat
}

func (t *buildTask) compileAndEmit(orchestrator *Orchestrator, path tspath.Path) {
orchestrator.buildSemaphore <- struct{}{} // Acquire a slot
defer func() { <-orchestrator.buildSemaphore }() // Release the slot
t.errors = nil
if orchestrator.opts.Command.BuildOptions.Verbose.IsTrue() {
t.reportStatus(ast.NewCompilerDiagnostic(diagnostics.Building_project_0, orchestrator.relativeFileName(t.config)))
Expand Down
12 changes: 9 additions & 3 deletions internal/execute/build/orchestrator.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,10 @@ type Orchestrator struct {
host *host

// order generation result
tasks *collections.SyncMap[tspath.Path, *buildTask]
order []string
errors []*ast.Diagnostic
tasks *collections.SyncMap[tspath.Path, *buildTask]
order []string
errors []*ast.Diagnostic
buildSemaphore chan struct{}

errorSummaryReporter tsc.DiagnosticsReporter
watchStatusReporter tsc.DiagnosticReporter
Expand Down Expand Up @@ -381,5 +382,10 @@ func NewOrchestrator(opts Options) *Orchestrator {
} else {
orchestrator.errorSummaryReporter = tsc.CreateReportErrorSummary(opts.Sys, opts.Command.CompilerOptions)
}
maxConcurrentProjects := tsoptions.TscMaxConcurrentProjectsOption.DefaultValueDescription.(int)
if opts.Command.BuildOptions.MaxConcurrentProjects != nil {
maxConcurrentProjects = *opts.Command.BuildOptions.MaxConcurrentProjects
}
orchestrator.buildSemaphore = make(chan struct{}, maxConcurrentProjects)
return orchestrator
}
25 changes: 20 additions & 5 deletions internal/execute/tsctests/tscbuild_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package tsctests
import (
"fmt"
"slices"
"strconv"
"strings"
"testing"
"time"
Expand Down Expand Up @@ -2163,7 +2164,7 @@ func TestBuildProjectsBuilding(t *testing.T) {
return files
}

getTestCases := func(pkgCount int) []*tscInput {
getTestCases := func(pkgCount int, maxBuilding int) []*tscInput {
edits := []*tscEdit{
{
caption: "dts doesn't change",
Expand All @@ -2188,21 +2189,35 @@ func TestBuildProjectsBuilding(t *testing.T) {
commandLineArgs: []string{"-b", "-v"},
edits: edits,
},
{
subScenario: fmt.Sprintf(`when there are %d projects in a solution with maxConcurrentProjects %d`, pkgCount, maxBuilding),
files: files(pkgCount),
cwd: "/user/username/projects/myproject",
commandLineArgs: []string{"-b", "-v", "--maxConcurrentProjects", strconv.Itoa(maxBuilding)},
edits: edits,
},
{
subScenario: fmt.Sprintf(`when there are %d projects in a solution`, pkgCount),
files: files(pkgCount),
cwd: "/user/username/projects/myproject",
commandLineArgs: []string{"-b", "-w", "-v"},
edits: edits,
},
{
subScenario: fmt.Sprintf(`when there are %d projects in a solution with maxConcurrentProjects %d`, pkgCount, maxBuilding),
files: files(pkgCount),
cwd: "/user/username/projects/myproject",
commandLineArgs: []string{"-b", "-w", "-v", "--maxConcurrentProjects", strconv.Itoa(maxBuilding)},
edits: edits,
},
}
}

testCases := slices.Concat(
getTestCases(3),
getTestCases(5),
getTestCases(8),
getTestCases(23),
getTestCases(3, 2),
getTestCases(5, 2),
getTestCases(8, 3),
getTestCases(23, 3),
)

for _, test := range testCases {
Expand Down
13 changes: 9 additions & 4 deletions internal/tsoptions/commandlineparser.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,12 @@ func ParseBuildCommandLine(
if result.CompilerOptions.Watch.IsTrue() && result.BuildOptions.Dry.IsTrue() {
result.Errors = append(result.Errors, ast.NewCompilerDiagnostic(diagnostics.Options_0_and_1_cannot_be_combined, "watch", "dry"))
}
if result.CompilerOptions.SingleThreaded.IsTrue() && result.BuildOptions.MaxConcurrentProjects != nil {
result.Errors = append(result.Errors, ast.NewCompilerDiagnostic(diagnostics.Options_0_and_1_cannot_be_combined, "singleThreaded", "maxConcurrentProjects"))
}
if result.BuildOptions.MaxConcurrentProjects != nil && *result.BuildOptions.MaxConcurrentProjects <= 0 {
result.Errors = append(result.Errors, ast.NewCompilerDiagnostic(diagnostics.Option_0_requires_value_to_be_greater_than_0, "maxConcurrentProjects"))
}

return result
}
Expand Down Expand Up @@ -141,7 +147,7 @@ func (p *commandLineParser) parseStrings(args []string) {
inputOptionName := getInputOptionName(s)
opt := p.optionsMap.GetOptionDeclarationFromName(inputOptionName, true /*allowShort*/)
if opt != nil {
i = p.parseOptionValue(args, i, opt, nil)
i = p.parseOptionValue(args, i, opt, p.workerDiagnostics.OptionTypeMismatchDiagnostic)
} else {
watchOpt := WatchNameMap.GetOptionDeclarationFromName(inputOptionName, true /*allowShort*/)
if watchOpt != nil {
Expand Down Expand Up @@ -255,9 +261,6 @@ func (p *commandLineParser) parseOptionValue(
// Check to see if no argument was provided (e.g. "--locale" is the last command-line argument).
if i >= len(args) {
if opt.Kind != "boolean" {
if diag == nil {
diag = p.workerDiagnostics.OptionTypeMismatchDiagnostic
}
p.errors = append(p.errors, ast.NewCompilerDiagnostic(diag, opt.Name, getCompilerOptionValueTypeString(opt)))
if opt.Kind == "list" {
p.options.Set(opt.Name, []string{})
Expand All @@ -276,6 +279,8 @@ func (p *commandLineParser) parseOptionValue(
num, e := strconv.Atoi(args[i])
if e == nil {
p.options.Set(opt.Name, num)
} else {
p.errors = append(p.errors, ast.NewCompilerDiagnostic(diag, opt.Name, getCompilerOptionValueTypeString(opt)))
}
i++
case "boolean":
Expand Down
35 changes: 30 additions & 5 deletions internal/tsoptions/commandlineparser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,11 +246,18 @@ func formatNewBaseline(
}

func (f commandLineSubScenario) assertBuildParseResult(t *testing.T) {
t.Helper()
f.assertBuildParseResultWithTsBaseline(t, func() *TestCommandLineParserBuild {
originalBaseline := f.baseline.ReadFile(t)
return parseExistingCompilerBaselineBuild(t, originalBaseline)
})
}

func (f commandLineSubScenario) assertBuildParseResultWithTsBaseline(t *testing.T, getTsBaseline func() *TestCommandLineParserBuild) {
t.Helper()
t.Run(f.testName, func(t *testing.T) {
t.Parallel()
originalBaseline := f.baseline.ReadFile(t)
tsBaseline := parseExistingCompilerBaselineBuild(t, originalBaseline)
tsBaseline := getTsBaseline()

// f.workerDiagnostic is either defined or set to default pointer in `createSubScenario`
parsed := tsoptions.ParseBuildCommandLine(f.commandLine, &tsoptionstest.VfsParseConfigHost{
Expand All @@ -259,19 +266,25 @@ func (f commandLineSubScenario) assertBuildParseResult(t *testing.T) {
})

newBaselineProjects := strings.Join(parsed.Projects, ",")
assert.Equal(t, tsBaseline.projects, newBaselineProjects)
if tsBaseline != nil {
assert.Equal(t, tsBaseline.projects, newBaselineProjects)
}

o, _ := json.Marshal(parsed.BuildOptions)
newParsedBuildOptions := &core.BuildOptions{}
e := json.Unmarshal(o, newParsedBuildOptions)
assert.NilError(t, e)
assert.DeepEqual(t, tsBaseline.options, newParsedBuildOptions, cmpopts.IgnoreUnexported(core.BuildOptions{}))
if tsBaseline != nil {
assert.DeepEqual(t, tsBaseline.options, newParsedBuildOptions, cmpopts.IgnoreUnexported(core.BuildOptions{}))
}

compilerOpts, _ := json.Marshal(parsed.CompilerOptions)
newParsedCompilerOptions := &core.CompilerOptions{}
e = json.Unmarshal(compilerOpts, newParsedCompilerOptions)
assert.NilError(t, e)
assert.DeepEqual(t, tsBaseline.compilerOptions, newParsedCompilerOptions, cmpopts.IgnoreUnexported(core.CompilerOptions{}))
if tsBaseline != nil {
assert.DeepEqual(t, tsBaseline.compilerOptions, newParsedCompilerOptions, cmpopts.IgnoreUnexported(core.CompilerOptions{}))
}

newParsedWatchOptions := core.WatchOptions{}
e = json.Unmarshal(o, &newParsedWatchOptions)
Expand Down Expand Up @@ -429,9 +442,21 @@ func TestParseBuildCommandLine(t *testing.T) {
{"errors on invalid excludeFiles", []string{"--excludeFiles", "**/../*"}},
}

extraScenarios := []*subScenarioInput{
{`parse --maxConcurrentProjects`, []string{"--maxConcurrentProjects", "2"}},
{`--singleThreaded and --maxConcurrentProjects together is invalid`, []string{"--singleThreaded", "--maxConcurrentProjects", "2"}},
{`reports error when --maxConcurrentProjects is 0`, []string{"--maxConcurrentProjects", "0"}},
{`reports error when --maxConcurrentProjects is negative`, []string{"--maxConcurrentProjects", "-1"}},
{`reports error when --maxConcurrentProjects is invalid type`, []string{"--maxConcurrentProjects", "invalid"}},
}

for _, testCase := range parseCommandLineSubScenarios {
testCase.createSubScenario("parseBuildOptions").assertBuildParseResult(t)
}

for _, testCase := range extraScenarios {
testCase.createSubScenario("parseBuildOptions").assertBuildParseResultWithTsBaseline(t, func() *TestCommandLineParserBuild { return nil })
}
}

func TestAffectsBuildInfo(t *testing.T) {
Expand Down
9 changes: 9 additions & 0 deletions internal/tsoptions/declsbuild.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@ var TscBuildOption = CommandLineOption{
DefaultValueDescription: false,
}

var TscMaxConcurrentProjectsOption = &CommandLineOption{
Name: "maxConcurrentProjects",
Category: diagnostics.Command_line_Options,
Description: diagnostics.Specify_the_maximum_number_of_projects_that_can_be_built_concurrently,
Kind: "number",
DefaultValueDescription: 4,
}

var optionsForBuild = []*CommandLineOption{
&TscBuildOption,
{
Expand Down Expand Up @@ -51,6 +59,7 @@ var optionsForBuild = []*CommandLineOption{
Kind: "boolean",
DefaultValueDescription: false,
},
TscMaxConcurrentProjectsOption,
{
Name: "stopBuildOnErrors",
Category: diagnostics.Command_line_Options,
Expand Down
2 changes: 2 additions & 0 deletions internal/tsoptions/parsinghelpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,8 @@ func ParseBuildOptions(key string, value any, allOptions *core.BuildOptions) []*
allOptions.StopBuildOnErrors = parseTristate(value)
case "verbose":
allOptions.Verbose = parseTristate(value)
case "maxConcurrentProjects":
allOptions.MaxConcurrentProjects = parseNumber(value)
}
return nil
}
Expand Down
3 changes: 3 additions & 0 deletions testdata/baselines/reference/tsbuild/commandLine/help.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,9 @@ Build all projects, including those that appear to be up to date.
--clean
Delete the outputs of all projects.

--maxConcurrentProjects
Specify the maximum number of projects that can be built concurrently.

--stopBuildOnErrors
Skip building downstream projects on error in upstream project.

Expand Down
Loading
Loading