private string GetDefaultRepositoryPath( IExecutionContext executionContext, RepositoryResource repository, TrackingConfig newConfig ) { string repoPath = String.Empty; string workDirectory = HostContext.GetDirectory(WellKnownDirectory.Work); if (RepositoryUtil.HasMultipleCheckouts(executionContext.JobSettings)) { // If we have multiple checkouts they should all be rooted to the sources directory (_work/1/s/repo1) var repoSourceDirectory = newConfig?.RepositoryTrackingInfo.Where(item => string.Equals(item.Identifier, repository.Alias, StringComparison.OrdinalIgnoreCase)).Select(item => item.SourcesDirectory).FirstOrDefault(); if (repoSourceDirectory != null) { repoPath = Path.Combine(workDirectory, repoSourceDirectory); } else { repoPath = Path.Combine(workDirectory, newConfig.SourcesDirectory, RepositoryUtil.GetCloneDirectory(repository)); } } else { // For single checkouts, the repository is rooted to the sources folder (_work/1/s) repoPath = Path.Combine(workDirectory, newConfig.SourcesDirectory); } return(repoPath); }
private string GetDefaultRepoLocalPathValue(IExecutionContext executionContext, IList<Pipelines.JobStep> steps, TrackingConfig trackingConfig, RepositoryInfo repoInfo) { string selfRepoPath = null; // For saving backward compatibility with the behavior of the Build.RepoLocalPath that was before this PR https://github.com/microsoft/azure-pipelines-agent/pull/3237 // We need to change how we set the default value of this variable // We need to allow the setting of paths from RepositoryTrackingInfo for checkout tasks where path input was provided by the user // and this input is not point to the default location for this repository // This is the only case where the value of Build.RepoLocalPath variable is not pointing to the root of sources directory /s. // The new logic is not affecting single checkout jobs and jobs with multiple checkouts and default paths for Self repository if (RepositoryUtil.HasMultipleCheckouts(executionContext.JobSettings)) { // get checkout task for self repo var selfCheckoutTask = GetSelfCheckoutTask(steps); // Check if the task has path input with custom path, if so we need to set as a value of selfRepoPath the value of SourcesDirectory from RepositoryTrackingInfo if (IsCheckoutToCustomPath(trackingConfig, repoInfo, selfCheckoutTask)) { selfRepoPath = trackingConfig.RepositoryTrackingInfo .Where(repo => RepositoryUtil.IsPrimaryRepositoryName(repo.Identifier)) .Select(props => props.SourcesDirectory).FirstOrDefault(); } } // For single checkout jobs and multicheckout jobs with default paths set selfRepoPath to the default sources directory if (selfRepoPath == null) { selfRepoPath = trackingConfig.SourcesDirectory; } return selfRepoPath; }
public void Execute(IExecutionContext context, Command command) { var eventProperties = command.Properties; var data = command.Data; String alias; if (!eventProperties.TryGetValue(PluginInternalUpdateRepositoryEventProperties.Alias, out alias) || String.IsNullOrEmpty(alias)) { throw new Exception(StringUtil.Loc("MissingRepositoryAlias")); } var repository = context.Repositories.FirstOrDefault(x => string.Equals(x.Alias, alias, StringComparison.OrdinalIgnoreCase)); if (repository == null) { throw new Exception(StringUtil.Loc("RepositoryNotExist")); } if (string.IsNullOrEmpty(data)) { throw new Exception(StringUtil.Loc("MissingRepositoryPath")); } var currentPath = repository.Properties.Get <string>(RepositoryPropertyNames.Path); if (!string.Equals(data.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar), currentPath.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar), IOUtil.FilePathStringComparison)) { string repositoryPath = data.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); repository.Properties.Set <string>(RepositoryPropertyNames.Path, repositoryPath); bool isSelfRepo = RepositoryUtil.IsPrimaryRepositoryName(repository.Alias); bool hasMultipleCheckouts = RepositoryUtil.HasMultipleCheckouts(context.JobSettings); if (isSelfRepo || !hasMultipleCheckouts) { var directoryManager = context.GetHostContext().GetService <IBuildDirectoryManager>(); string _workDirectory = context.GetHostContext().GetDirectory(WellKnownDirectory.Work); var trackingConfig = directoryManager.UpdateDirectory(context, repository); if (hasMultipleCheckouts) { // In Multi-checkout, we don't want to reset sources dir or default working dir. // So, we will just reset the repo local path string buildDirectory = context.Variables.Get(Constants.Variables.Pipeline.Workspace); string repoRelativePath = directoryManager.GetRelativeRepositoryPath(buildDirectory, repositoryPath); context.SetVariable(Constants.Variables.Build.RepoLocalPath, Path.Combine(_workDirectory, repoRelativePath), isFilePath: true); } else { // If we only have a single repository, then update all the paths to point to it. context.SetVariable(Constants.Variables.Build.SourcesDirectory, repositoryPath, isFilePath: true); context.SetVariable(Constants.Variables.Build.RepoLocalPath, repositoryPath, isFilePath: true); context.SetVariable(Constants.Variables.System.DefaultWorkingDirectory, repositoryPath, isFilePath: true); } } } repository.Properties.Set("__AZP_READY", bool.TrueString); }
public void HasMultipleCheckouts_should_not_throw() { using (TestHostContext hc = new TestHostContext(this)) { Tracing trace = hc.GetTrace(); Assert.Equal(false, RepositoryUtil.HasMultipleCheckouts(null)); var dict = new Dictionary <string, string>(StringComparer.OrdinalIgnoreCase); Assert.Equal(false, RepositoryUtil.HasMultipleCheckouts(dict)); dict.Add("x", "y"); Assert.Equal(false, RepositoryUtil.HasMultipleCheckouts(dict)); dict.Add(WellKnownJobSettings.HasMultipleCheckouts, "burger"); Assert.Equal(false, RepositoryUtil.HasMultipleCheckouts(dict)); } }
public void HasMultipleCheckouts_should_return_true_when_set_correctly() { using (TestHostContext hc = new TestHostContext(this)) { Tracing trace = hc.GetTrace(); Assert.Equal(false, RepositoryUtil.HasMultipleCheckouts(null)); var dict = new Dictionary <string, string>(StringComparer.OrdinalIgnoreCase); dict[WellKnownJobSettings.HasMultipleCheckouts] = "true"; Assert.Equal(true, RepositoryUtil.HasMultipleCheckouts(dict)); dict[WellKnownJobSettings.HasMultipleCheckouts] = "TRUE"; Assert.Equal(true, RepositoryUtil.HasMultipleCheckouts(dict)); dict[WellKnownJobSettings.HasMultipleCheckouts] = "True"; Assert.Equal(true, RepositoryUtil.HasMultipleCheckouts(dict)); } }
private string GetDefaultRepositoryPath( IExecutionContext executionContext, RepositoryResource repository, string defaultSourcesDirectory) { if (RepositoryUtil.HasMultipleCheckouts(executionContext.JobSettings)) { // If we have multiple checkouts they should all be rooted to the sources directory (_work/1/s/repo1) return(Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Work), defaultSourcesDirectory, RepositoryUtil.GetCloneDirectory(repository))); } else { // For single checkouts, the repository is rooted to the sources folder (_work/1/s) return(Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Work), defaultSourcesDirectory)); } }
private void ProcessPluginInternalUpdateRepositoryPathCommand(IExecutionContext context, Dictionary <string, string> eventProperties, string data) { String alias; if (!eventProperties.TryGetValue(PluginInternalUpdateRepositoryEventProperties.Alias, out alias) || String.IsNullOrEmpty(alias)) { throw new Exception(StringUtil.Loc("MissingRepositoryAlias")); } var repository = context.Repositories.FirstOrDefault(x => string.Equals(x.Alias, alias, StringComparison.OrdinalIgnoreCase)); if (repository == null) { throw new Exception(StringUtil.Loc("RepositoryNotExist")); } if (string.IsNullOrEmpty(data)) { throw new Exception(StringUtil.Loc("MissingRepositoryPath")); } var currentPath = repository.Properties.Get <string>(RepositoryPropertyNames.Path); if (!string.Equals(data.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar), currentPath.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar), IOUtil.FilePathStringComparison)) { repository.Properties.Set <string>(RepositoryPropertyNames.Path, data.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)); var directoryManager = HostContext.GetService <IBuildDirectoryManager>(); var trackingConfig = directoryManager.UpdateDirectory(context, repository); // Set the directory variables. string _workDirectory = HostContext.GetDirectory(WellKnownDirectory.Work); context.SetVariable(Constants.Variables.Build.SourcesDirectory, Path.Combine(_workDirectory, trackingConfig.SourcesDirectory), isFilePath: true); context.SetVariable(Constants.Variables.Build.RepoLocalPath, Path.Combine(_workDirectory, trackingConfig.SourcesDirectory), isFilePath: true); // Only set the working directory here if we are NOT in Multi-checkout if (!RepositoryUtil.HasMultipleCheckouts(context.JobSettings)) { context.SetVariable(Constants.Variables.System.DefaultWorkingDirectory, Path.Combine(_workDirectory, trackingConfig.SourcesDirectory), isFilePath: true); } } repository.Properties.Set("__AZP_READY", bool.TrueString); }
// 1. use source provide to solve path, if solved result is rooted, return full path. // 2. prefix default path root (build.sourcesDirectory), if result is rooted, return full path. public override string GetRootedPath(IExecutionContext context, string path) { string rootedPath = null; TryGetRepositoryInfo(context, out RepositoryInfo repoInfo); if (repoInfo.SourceProvider != null && repoInfo.Repository != null && StringUtil.ConvertToBoolean(repoInfo.Repository.Properties.Get <string>("__AZP_READY"))) { path = repoInfo.SourceProvider.GetLocalPath(context, repoInfo.Repository, path) ?? string.Empty; Trace.Info($"Build JobExtension resolving path use source provide: {path}"); if (!string.IsNullOrEmpty(path) && path.IndexOfAny(Path.GetInvalidPathChars()) < 0 && Path.IsPathRooted(path)) { try { rootedPath = Path.GetFullPath(path); Trace.Info($"Path resolved by source provider is a rooted path, return absolute path: {rootedPath}"); return(rootedPath); } catch (Exception ex) { Trace.Info($"Path resolved by source provider is a rooted path, but it is not a full qualified path: {path}"); Trace.Error(ex); } } } string defaultPathRoot = null; if (RepositoryUtil.HasMultipleCheckouts(context.JobSettings)) { // If there are multiple checkouts, set the default directory to the sources root folder (_work/1/s) defaultPathRoot = context.Variables.Get(Constants.Variables.Build.SourcesDirectory); Trace.Info($"The Default Path Root of Build JobExtension is build.sourcesDirectory: {defaultPathRoot}"); } else if (repoInfo.Repository != null) { // If there is only one checkout/repository, set it to the repository path defaultPathRoot = repoInfo.Repository.Properties.Get <string>(Pipelines.RepositoryPropertyNames.Path); Trace.Info($"The Default Path Root of Build JobExtension is repository.path: {defaultPathRoot}"); } if (defaultPathRoot != null && defaultPathRoot.IndexOfAny(Path.GetInvalidPathChars()) < 0 && path != null && path.IndexOfAny(Path.GetInvalidPathChars()) < 0) { path = Path.Combine(defaultPathRoot, path); Trace.Info($"After prefix Default Path Root provide by JobExtension: {path}"); if (Path.IsPathRooted(path)) { try { rootedPath = Path.GetFullPath(path); Trace.Info($"Return absolute path after prefix DefaultPathRoot: {rootedPath}"); return(rootedPath); } catch (Exception ex) { Trace.Error(ex); Trace.Info($"After prefix Default Path Root provide by JobExtension, the Path is a rooted path, but it is not full qualified, return the path: {path}."); return(path); } } } return(rootedPath); }
public TrackingConfig PrepareDirectory( IExecutionContext executionContext, IList <RepositoryResource> repositories, WorkspaceOptions workspace) { // Validate parameters. Trace.Entering(); ArgUtil.NotNull(executionContext, nameof(executionContext)); ArgUtil.NotNull(executionContext.Variables, nameof(executionContext.Variables)); ArgUtil.NotNull(repositories, nameof(repositories)); var trackingManager = HostContext.GetService <ITrackingManager>(); // Create the tracking config for this execution of the pipeline var agentSettings = HostContext.GetService <IConfigurationStore>().GetSettings(); var newConfig = trackingManager.Create(executionContext, repositories, ShouldOverrideBuildDirectory(repositories, agentSettings)); // Load the tracking config from the last execution of the pipeline var existingConfig = trackingManager.LoadExistingTrackingConfig(executionContext); // If there aren't any major changes, merge the configurations and use the same workspace if (trackingManager.AreTrackingConfigsCompatible(executionContext, newConfig, existingConfig)) { newConfig = trackingManager.MergeTrackingConfigs(executionContext, newConfig, existingConfig); } else if (existingConfig != null) { // If the previous config had different repos, get a new workspace folder and mark the old one for clean up trackingManager.MarkForGarbageCollection(executionContext, existingConfig); // If the config file was updated to a new config, we need to delete the legacy artifact/staging directories. // DeleteDirectory will check for the existence of the folders first. DeleteDirectory( executionContext, description: "legacy artifacts directory", path: Path.Combine(existingConfig.BuildDirectory, Constants.Build.Path.LegacyArtifactsDirectory)); DeleteDirectory( executionContext, description: "legacy staging directory", path: Path.Combine(existingConfig.BuildDirectory, Constants.Build.Path.LegacyStagingDirectory)); } // Save any changes to the config file trackingManager.UpdateTrackingConfig(executionContext, newConfig); // Prepare the build directory. // There are 2 ways to provide build directory clean policy. // 1> set definition variable build.clean or agent.clean.buildDirectory. (on-prem user need to use this, since there is no Web UI in TFS 2016) // 2> select source clean option in definition repository tab. (VSTS will have this option in definition designer UI) BuildCleanOption cleanOption = GetBuildDirectoryCleanOption(executionContext, workspace); CreateDirectory( executionContext, description: "build directory", path: Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Work), newConfig.BuildDirectory), deleteExisting: cleanOption == BuildCleanOption.All); CreateDirectory( executionContext, description: "artifacts directory", path: Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Work), newConfig.ArtifactsDirectory), deleteExisting: true); CreateDirectory( executionContext, description: "test results directory", path: Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Work), newConfig.TestResultsDirectory), deleteExisting: true); CreateDirectory( executionContext, description: "binaries directory", path: Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Work), newConfig.BuildDirectory, Constants.Build.Path.BinariesDirectory), deleteExisting: cleanOption == BuildCleanOption.Binary); var defaultSourceDirectory = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Work), newConfig.SourcesDirectory); CreateDirectory( executionContext, description: "source directory", path: defaultSourceDirectory, deleteExisting: cleanOption == BuildCleanOption.Source); // Set the default clone path for each repository (the Checkout task may override this later) foreach (var repository in repositories) { String repoPath; if (RepositoryUtil.HasMultipleCheckouts(executionContext.JobSettings)) { // If we have multiple checkouts they should all be rooted to the sources directory (_work/1/s/repo1) var repoSourceDirectory = newConfig?.RepositoryTrackingInfo.Where(item => string.Equals(item.Identifier, repository.Alias, StringComparison.OrdinalIgnoreCase)).Select(item => item.SourcesDirectory).FirstOrDefault(); if (repoSourceDirectory != null) { repoPath = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Work), repoSourceDirectory); } else { repoPath = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Work), newConfig.SourcesDirectory, RepositoryUtil.GetCloneDirectory(repository)); } } else { // For single checkouts, the repository is rooted to the sources folder (_work/1/s) repoPath = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Work), newConfig.SourcesDirectory); } if (!string.Equals(repoPath, defaultSourceDirectory, StringComparison.Ordinal)) { CreateDirectory( executionContext, description: "repository source directory", path: repoPath, deleteExisting: cleanOption == BuildCleanOption.Source); } Trace.Info($"Set repository path for repository {repository.Alias} to '{repoPath}'"); repository.Properties.Set <string>(RepositoryPropertyNames.Path, repoPath); } return(newConfig); }
protected bool HasMultipleCheckouts(AgentTaskPluginExecutionContext executionContext) { return(executionContext != null && RepositoryUtil.HasMultipleCheckouts(executionContext.JobSettings)); }
public void Execute(IExecutionContext context, Command command) { ArgUtil.NotNull(context, nameof(context)); ArgUtil.NotNull(command, nameof(command)); var eventProperties = command.Properties; var data = command.Data; String alias; if (!eventProperties.TryGetValue(PluginInternalUpdateRepositoryEventProperties.Alias, out alias) || String.IsNullOrEmpty(alias)) { throw new ArgumentNullException(StringUtil.Loc("MissingRepositoryAlias")); } var repository = context.Repositories.FirstOrDefault(x => string.Equals(x.Alias, alias, StringComparison.OrdinalIgnoreCase)); if (repository == null) { throw new ArgumentNullException(StringUtil.Loc("RepositoryNotExist")); } if (string.IsNullOrEmpty(data)) { throw new ArgumentNullException(StringUtil.Loc("MissingRepositoryPath")); } var currentPath = repository.Properties.Get <string>(RepositoryPropertyNames.Path); if (!string.Equals(data.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar), currentPath.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar), IOUtil.FilePathStringComparison)) { string repositoryPath = data.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); repository.Properties.Set <string>(RepositoryPropertyNames.Path, repositoryPath); bool isSelfRepo = RepositoryUtil.IsPrimaryRepositoryName(repository.Alias); bool hasMultipleCheckouts = RepositoryUtil.HasMultipleCheckouts(context.JobSettings); var directoryManager = context.GetHostContext().GetService <IBuildDirectoryManager>(); string _workDirectory = context.GetHostContext().GetDirectory(WellKnownDirectory.Work); var trackingConfig = directoryManager.UpdateDirectory(context, repository); if (isSelfRepo || !hasMultipleCheckouts) { if (hasMultipleCheckouts) { // In Multi-checkout, we don't want to reset sources dir or default working dir. // So, we will just reset the repo local path string buildDirectory = context.Variables.Get(Constants.Variables.Pipeline.Workspace); string repoRelativePath = directoryManager.GetRelativeRepositoryPath(buildDirectory, repositoryPath); string sourcesDirectory = context.Variables.Get(Constants.Variables.Build.SourcesDirectory); string repoLocalPath = context.Variables.Get(Constants.Variables.Build.RepoLocalPath); string newRepoLocation = Path.Combine(_workDirectory, repoRelativePath); // For saving backward compatibility with the behavior of the Build.RepoLocalPath that was before this PR https://github.com/microsoft/azure-pipelines-agent/pull/3237 // we need to deny updating of the variable in case the new path is the default location for the repository that is equal to sourcesDirectory/repository.Name // since the variable already has the right value in this case and pointing to the default sources location if (repoLocalPath == null || !string.Equals(newRepoLocation, Path.Combine(sourcesDirectory, repository.Name), IOUtil.FilePathStringComparison)) { context?.SetVariable(Constants.Variables.Build.RepoLocalPath, newRepoLocation, isFilePath: true); } } else { // If we only have a single repository, then update all the paths to point to it. context.SetVariable(Constants.Variables.Build.SourcesDirectory, repositoryPath, isFilePath: true); context.SetVariable(Constants.Variables.Build.RepoLocalPath, repositoryPath, isFilePath: true); context.SetVariable(Constants.Variables.System.DefaultWorkingDirectory, repositoryPath, isFilePath: true); } } } repository.Properties.Set("__AZP_READY", bool.TrueString); }
// Prepare build directory // Set all build related variables public override void InitializeJobExtension(IExecutionContext executionContext, IList <Pipelines.JobStep> steps, Pipelines.WorkspaceOptions workspace) { // Validate args. Trace.Entering(); ArgUtil.NotNull(executionContext, nameof(executionContext)); // This flag can be false for jobs like cleanup artifacts. // If syncSources = false, we will not set source related build variable, not create build folder, not sync source. bool syncSources = executionContext.Variables.Build_SyncSources ?? true; if (!syncSources) { Trace.Info($"{Constants.Variables.Build.SyncSources} = false, we will not set source related build variable, not create build folder and not sync source"); return; } // We set the variables based on the 'self' repository if (!TryGetRepositoryInfo(executionContext, out RepositoryInfo repoInfo)) { throw new Exception(StringUtil.Loc("SupportedRepositoryEndpointNotFound")); } executionContext.Debug($"Primary repository: {repoInfo.Repository.Properties.Get<string>(Pipelines.RepositoryPropertyNames.Name)}. repository type: {repoInfo.Repository.Type}"); // Set the repo variables. if (!string.IsNullOrEmpty(repoInfo.Repository.Id)) // TODO: Move to const after source artifacts PR is merged. { executionContext.Variables.Set(Constants.Variables.Build.RepoId, repoInfo.Repository.Id); } executionContext.Variables.Set(Constants.Variables.Build.RepoName, repoInfo.Repository.Properties.Get <string>(Pipelines.RepositoryPropertyNames.Name)); executionContext.Variables.Set(Constants.Variables.Build.RepoProvider, ConvertToLegacyRepositoryType(repoInfo.Repository.Type)); executionContext.Variables.Set(Constants.Variables.Build.RepoUri, repoInfo.Repository.Url?.AbsoluteUri); // There may be more than one Checkout task, but for back compat we will simply pay attention to the first checkout task here var checkoutTask = steps.FirstOrDefault(x => x.IsCheckoutTask()) as Pipelines.TaskStep; if (checkoutTask != null) { if (checkoutTask.Inputs.ContainsKey(Pipelines.PipelineConstants.CheckoutTaskInputs.Submodules)) { executionContext.Variables.Set(Constants.Variables.Build.RepoGitSubmoduleCheckout, Boolean.TrueString); } else { executionContext.Variables.Set(Constants.Variables.Build.RepoGitSubmoduleCheckout, Boolean.FalseString); } // overwrite primary repository's clean value if build.repository.clean is sent from server. this is used by tfvc gated check-in bool?repoClean = executionContext.Variables.GetBoolean(Constants.Variables.Build.RepoClean); if (repoClean != null) { checkoutTask.Inputs[Pipelines.PipelineConstants.CheckoutTaskInputs.Clean] = repoClean.Value.ToString(); } else { if (checkoutTask.Inputs.ContainsKey(Pipelines.PipelineConstants.CheckoutTaskInputs.Clean)) { executionContext.Variables.Set(Constants.Variables.Build.RepoClean, checkoutTask.Inputs[Pipelines.PipelineConstants.CheckoutTaskInputs.Clean]); } else { executionContext.Variables.Set(Constants.Variables.Build.RepoClean, Boolean.FalseString); } } } // Prepare the build directory. executionContext.Output(StringUtil.Loc("PrepareBuildDir")); var directoryManager = HostContext.GetService <IBuildDirectoryManager>(); TrackingConfig trackingConfig = directoryManager.PrepareDirectory( executionContext, executionContext.Repositories, workspace); // Set the directory variables. executionContext.Output(StringUtil.Loc("SetBuildVars")); string _workDirectory = HostContext.GetDirectory(WellKnownDirectory.Work); executionContext.SetVariable(Constants.Variables.Agent.BuildDirectory, Path.Combine(_workDirectory, trackingConfig.BuildDirectory), isFilePath: true); executionContext.SetVariable(Constants.Variables.System.ArtifactsDirectory, Path.Combine(_workDirectory, trackingConfig.ArtifactsDirectory), isFilePath: true); executionContext.SetVariable(Constants.Variables.Common.TestResultsDirectory, Path.Combine(_workDirectory, trackingConfig.TestResultsDirectory), isFilePath: true); executionContext.SetVariable(Constants.Variables.Build.BinariesDirectory, Path.Combine(_workDirectory, trackingConfig.BuildDirectory, Constants.Build.Path.BinariesDirectory), isFilePath: true); executionContext.SetVariable(Constants.Variables.Build.SourcesDirectory, Path.Combine(_workDirectory, trackingConfig.SourcesDirectory), isFilePath: true); executionContext.SetVariable(Constants.Variables.Build.StagingDirectory, Path.Combine(_workDirectory, trackingConfig.ArtifactsDirectory), isFilePath: true); executionContext.SetVariable(Constants.Variables.Build.ArtifactStagingDirectory, Path.Combine(_workDirectory, trackingConfig.ArtifactsDirectory), isFilePath: true); executionContext.SetVariable(Constants.Variables.Build.RepoLocalPath, Path.Combine(_workDirectory, trackingConfig.SourcesDirectory), isFilePath: true); executionContext.SetVariable(Constants.Variables.Pipeline.Workspace, Path.Combine(_workDirectory, trackingConfig.BuildDirectory), isFilePath: true); if (RepositoryUtil.HasMultipleCheckouts(executionContext.JobSettings)) { // If there are multiple checkouts, set the working directory to root folder (_work/1) executionContext.SetVariable(Constants.Variables.System.DefaultWorkingDirectory, Path.Combine(_workDirectory, trackingConfig.BuildDirectory), isFilePath: true); } else { // If there is only one (or none) checkout, set to the normal default sources path (_work/1/s) executionContext.SetVariable(Constants.Variables.System.DefaultWorkingDirectory, Path.Combine(_workDirectory, trackingConfig.SourcesDirectory), isFilePath: true); } }