Skip to content

Commit 1b8dc68

Browse files
authored
Auto-add activation dependency to custom jobs without explicit needs (#3458)
1 parent 0e6c1f3 commit 1b8dc68

3 files changed

Lines changed: 150 additions & 2 deletions

File tree

.github/workflows/super-linter-report.lock.yml

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/workflow/compiler_jobs.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ func (c *Compiler) buildJobs(data *WorkflowData, markdownPath string) error {
109109
}
110110

111111
// Build additional custom jobs from frontmatter jobs section
112-
if err := c.buildCustomJobs(data); err != nil {
112+
if err := c.buildCustomJobs(data, activationJobCreated); err != nil {
113113
return fmt.Errorf("failed to build custom jobs: %w", err)
114114
}
115115

@@ -730,15 +730,17 @@ func (c *Compiler) extractJobsFromFrontmatter(frontmatter map[string]any) map[st
730730
}
731731

732732
// buildCustomJobs creates custom jobs defined in the frontmatter jobs section
733-
func (c *Compiler) buildCustomJobs(data *WorkflowData) error {
733+
func (c *Compiler) buildCustomJobs(data *WorkflowData, activationJobCreated bool) error {
734734
for jobName, jobConfig := range data.Jobs {
735735
if configMap, ok := jobConfig.(map[string]any); ok {
736736
job := &Job{
737737
Name: jobName,
738738
}
739739

740740
// Extract job dependencies
741+
hasExplicitNeeds := false
741742
if needs, hasNeeds := configMap["needs"]; hasNeeds {
743+
hasExplicitNeeds = true
742744
if needsList, ok := needs.([]any); ok {
743745
for _, need := range needsList {
744746
if needStr, ok := need.(string); ok {
@@ -751,6 +753,13 @@ func (c *Compiler) buildCustomJobs(data *WorkflowData) error {
751753
}
752754
}
753755

756+
// If no explicit needs and activation job exists, automatically add activation as dependency
757+
// This ensures custom jobs wait for workflow validation before executing
758+
if !hasExplicitNeeds && activationJobCreated {
759+
job.Needs = append(job.Needs, constants.ActivationJobName)
760+
log.Printf("Added automatic dependency: custom job '%s' now depends on '%s'", jobName, constants.ActivationJobName)
761+
}
762+
754763
// Extract other job properties
755764
if runsOn, hasRunsOn := configMap["runs-on"]; hasRunsOn {
756765
if runsOnStr, ok := runsOn.(string); ok {

pkg/workflow/jobs_test.go

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -613,3 +613,140 @@ func TestJobManager_GenerateMermaidGraph(t *testing.T) {
613613
})
614614
}
615615
}
616+
617+
func TestBuildCustomJobsActivationDependency(t *testing.T) {
618+
tests := []struct {
619+
name string
620+
jobs map[string]any
621+
activationJobCreated bool
622+
expectedDependencies map[string][]string
623+
description string
624+
}{
625+
{
626+
name: "custom job without explicit needs should depend on activation",
627+
jobs: map[string]any{
628+
"super_linter": map[string]any{
629+
"runs-on": "ubuntu-latest",
630+
"steps": []any{
631+
map[string]any{
632+
"name": "Run linter",
633+
"run": "echo 'linting'",
634+
},
635+
},
636+
},
637+
},
638+
activationJobCreated: true,
639+
expectedDependencies: map[string][]string{
640+
"super_linter": {"activation"},
641+
},
642+
description: "Custom job without explicit needs should automatically depend on activation",
643+
},
644+
{
645+
name: "custom job with explicit needs should not get activation dependency",
646+
jobs: map[string]any{
647+
"custom_job": map[string]any{
648+
"runs-on": "ubuntu-latest",
649+
"needs": []any{"other_job"},
650+
"steps": []any{
651+
map[string]any{
652+
"name": "Run custom",
653+
"run": "echo 'custom'",
654+
},
655+
},
656+
},
657+
},
658+
activationJobCreated: true,
659+
expectedDependencies: map[string][]string{
660+
"custom_job": {"other_job"},
661+
},
662+
description: "Custom job with explicit needs should keep its own dependencies",
663+
},
664+
{
665+
name: "custom job without activation should have no automatic dependency",
666+
jobs: map[string]any{
667+
"custom_job": map[string]any{
668+
"runs-on": "ubuntu-latest",
669+
"steps": []any{
670+
map[string]any{
671+
"name": "Run custom",
672+
"run": "echo 'custom'",
673+
},
674+
},
675+
},
676+
},
677+
activationJobCreated: false,
678+
expectedDependencies: map[string][]string{
679+
"custom_job": nil,
680+
},
681+
description: "Custom job should not have activation dependency when activation job doesn't exist",
682+
},
683+
{
684+
name: "multiple custom jobs without explicit needs",
685+
jobs: map[string]any{
686+
"linter": map[string]any{
687+
"runs-on": "ubuntu-latest",
688+
"steps": []any{
689+
map[string]any{"name": "Lint", "run": "lint"},
690+
},
691+
},
692+
"formatter": map[string]any{
693+
"runs-on": "ubuntu-latest",
694+
"steps": []any{
695+
map[string]any{"name": "Format", "run": "fmt"},
696+
},
697+
},
698+
},
699+
activationJobCreated: true,
700+
expectedDependencies: map[string][]string{
701+
"linter": {"activation"},
702+
"formatter": {"activation"},
703+
},
704+
description: "Multiple custom jobs should all depend on activation",
705+
},
706+
}
707+
708+
for _, tt := range tests {
709+
t.Run(tt.name, func(t *testing.T) {
710+
c := &Compiler{
711+
jobManager: NewJobManager(),
712+
}
713+
714+
data := &WorkflowData{
715+
Jobs: tt.jobs,
716+
}
717+
718+
err := c.buildCustomJobs(data, tt.activationJobCreated)
719+
if err != nil {
720+
t.Fatalf("%s: buildCustomJobs() error = %v", tt.description, err)
721+
}
722+
723+
// Verify each job has expected dependencies
724+
for jobName, expectedNeeds := range tt.expectedDependencies {
725+
job, exists := c.jobManager.jobs[jobName]
726+
if !exists {
727+
t.Fatalf("%s: job '%s' not found in job manager", tt.description, jobName)
728+
}
729+
730+
if len(job.Needs) != len(expectedNeeds) {
731+
t.Errorf("%s: job '%s' has %d dependencies, expected %d. Got: %v, Expected: %v",
732+
tt.description, jobName, len(job.Needs), len(expectedNeeds), job.Needs, expectedNeeds)
733+
continue
734+
}
735+
736+
if expectedNeeds == nil {
737+
if len(job.Needs) > 0 {
738+
t.Errorf("%s: job '%s' should have no dependencies, got: %v", tt.description, jobName, job.Needs)
739+
}
740+
continue
741+
}
742+
743+
for i, expected := range expectedNeeds {
744+
if job.Needs[i] != expected {
745+
t.Errorf("%s: job '%s' dependency[%d] = %s, expected %s",
746+
tt.description, jobName, i, job.Needs[i], expected)
747+
}
748+
}
749+
}
750+
})
751+
}
752+
}

0 commit comments

Comments
 (0)