func()

in common/build.go [518:625]


func (b *Build) executeScript(ctx context.Context, trace JobTrace, executor Executor) error {
	// track job start and create referees
	startTime := time.Now()
	b.createReferees(executor)

	// Prepare stage
	err := b.executeStage(ctx, BuildStagePrepare, executor)
	if err != nil {
		return asRunnerSystemFailure(fmt.Errorf(
			"prepare environment: %w. "+
				"Check https://docs.gitlab.com/runner/shells/#shell-profile-loading for more information", err))
	}

	err = asRunnerSystemFailure(b.attemptExecuteStage(ctx, BuildStageGetSources, executor, b.GetGetSourcesAttempts(), func(attempt int) error {
		if attempt == 1 {
			// If GetSources fails we delete all tracked and untracked files. This is
			// because Git's submodule support has various bugs that cause fetches to
			// fail if submodules have changed.
			return b.executeStage(ctx, BuildStageClearWorktree, executor)
		}

		return nil
	}))

	if err == nil {
		err = asRunnerSystemFailure(b.attemptExecuteStage(ctx, BuildStageRestoreCache, executor, b.GetRestoreCacheAttempts(), nil))
	}
	if err == nil {
		err = asRunnerSystemFailure(b.attemptExecuteStage(ctx, BuildStageDownloadArtifacts, executor, b.GetDownloadArtifactsAttempts(), nil))
	}

	//nolint:nestif
	if err == nil {
		timeouts := b.getStageTimeoutContexts(ctx,
			stageTimeout{"RUNNER_SCRIPT_TIMEOUT", 0},
			stageTimeout{"RUNNER_AFTER_SCRIPT_TIMEOUT", AfterScriptTimeout})

		scriptCtx, cancel := timeouts["RUNNER_SCRIPT_TIMEOUT"]()
		defer cancel()

		// update trace's cancel function so that the main script can be cancelled,
		// with after_script and later stages to still complete.
		trace.SetCancelFunc(cancel)

		for _, s := range b.Steps {
			// after_script has a separate BuildStage. See common.BuildStageAfterScript
			if s.Name == StepNameAfterScript {
				continue
			}
			err = b.executeStage(scriptCtx, StepToBuildStage(s), executor)
			if err != nil {
				break
			}
		}

		switch {
		// if parent context is fine but script context was cancelled we ensure the build error
		// failure reason is "canceled".
		case ctx.Err() == nil && errors.Is(scriptCtx.Err(), context.Canceled):
			err = &BuildError{
				Inner:         ErrJobCanceled,
				FailureReason: JobCanceled,
			}

			b.logger.Warningln("script canceled externally (UI, API)")

		// If the parent context reached deadline, don't do anything different than usual.
		// If the script context reached deadline, return the deadline error.
		case !errors.Is(ctx.Err(), context.DeadlineExceeded) && errors.Is(scriptCtx.Err(), context.DeadlineExceeded):
			err = &BuildError{
				Inner:         fmt.Errorf("%w: %w", ErrJobScriptTimeout, scriptCtx.Err()),
				FailureReason: JobExecutionTimeout,
			}
		}

		afterScriptCtx, cancel := timeouts["RUNNER_AFTER_SCRIPT_TIMEOUT"]()
		defer cancel()

		if afterScriptErr := b.executeAfterScript(afterScriptCtx, err, executor); afterScriptErr != nil {
			// the parent deadline being exceeded is reported at a later stage, so we
			// only focus on errors specific to after_script here.
			if !errors.Is(ctx.Err(), context.DeadlineExceeded) {
				// By default after-script ignores errors, but this can
				// be disabled via the AFTER_SCRIPT_IGNORE_ERRORS variable.

				if b.Settings().AfterScriptIgnoreErrors {
					b.logger.Warningln("after_script failed, but job will continue unaffected:", afterScriptErr)
				} else if err == nil {
					// If there's an existing error don't overwrite it with
					// the after-script error.
					err = afterScriptErr
				}
			}
		}
	}

	archiveCacheErr := asRunnerSystemFailure(b.executeArchiveCache(ctx, err, executor))

	artifactUploadErr := asRunnerSystemFailure(b.executeUploadArtifacts(ctx, err, executor))

	// track job end and execute referees
	endTime := time.Now()
	b.executeUploadReferees(ctx, startTime, endTime)

	b.removeFileBasedVariables(ctx, executor)

	return b.pickPriorityError(err, archiveCacheErr, artifactUploadErr)
}