public string GetOutputPath(Configuration solutionConfiguration) { // obtain project configuration (corresponding with solution configuration) ConfigurationBase config = BuildConfigurations[solutionConfiguration]; if (config == null) { return(null); } return(config.OutputPath); }
private string CreateWrapper(ConfigurationBase config) { // if wrapper assembly was created during the current build, then // there's no need to create it again if (IsCreated) { return(WrapperAssembly); } // synchronize build and output directory Sync(config); switch (WrapperTool) { case "primary": // nothing to do for Primary Interop Assembly break; case "tlbimp": if (PrimaryInteropAssembly != null) { // if tlbimp is defined as import tool, but a primary // interop assembly is available, then output a // warning Log(Level.Warning, "The component \"{0}\", referenced by" + " project \"{1}\" has an updated custom wrapper" + " available.", Name, Parent.Name); } ImportTypeLibrary(); break; case "aximp": ImportActiveXLibrary(); break; default: throw new BuildException(string.Format(CultureInfo.InvariantCulture, "Wrapper tool \"{0}\" for reference \"{1}\" in project" + " \"{2}\" is not supported.", WrapperTool, Name, Parent.Name), Location.UnknownLocation); } // mark wrapper as completed _isCreated = true; return(WrapperAssembly); }
/// <summary> /// Prepares the project for being built. /// </summary> /// <param name="solutionConfiguration">The solution configuration that is built.</param> /// <remarks> /// Ensures the configuration-level object directory exists and ensures /// that none of the output files are marked read-only. /// </remarks> protected override void Prepare(Configuration solutionConfiguration) { // Visual J#.NET uses the <project dir>\obj\<configuration> // as working directory, so we should do the same to make // sure relative paths are resolved correctly // (eg. AssemblyKeyFile attribute) // obtain project configuration (corresponding with solution configuration) ConfigurationBase config = BuildConfigurations[solutionConfiguration]; // ensure configuration-level object directory exists if (!config.ObjectDir.Exists) { config.ObjectDir.Create(); config.ObjectDir.Refresh(); } }
/// <summary> /// Returns a <see cref="ProcessStartInfo" /> for launching the compiler /// for this project. /// </summary> /// <param name="config">The configuration to build.</param> /// <param name="responseFile">The response file for the compiler.</param> /// <returns> /// A <see cref="ProcessStartInfo" /> for launching the compiler for /// this project. /// </returns> protected override ProcessStartInfo GetProcessStartInfo(ConfigurationBase config, string responseFile) { ProcessStartInfo psi = new ProcessStartInfo(FileUtils.CombinePaths(SolutionTask. Project.TargetFramework.FrameworkDirectory.FullName, "vbc.exe"), "@\"" + responseFile + "\""); // Visual Basic.NET uses the directory from which VS.NET // was launched as working directory, the closest match // and best behaviour for us is to use the <solution dir> // as working directory and fallback to the <project dir> // if the project was explicitly specified if (SolutionTask.SolutionFile != null) { psi.WorkingDirectory = SolutionTask.SolutionFile.DirectoryName; } else { psi.WorkingDirectory = ProjectDirectory.FullName; } return(psi); }
/// <summary> /// Gets the complete set of assemblies that need to be referenced when /// a project references this component. /// </summary> /// <param name="solutionConfiguration">The solution configuration that is built.</param> /// <returns> /// The complete set of assemblies that need to be referenced when a /// project references this component. /// </returns> public override StringCollection GetAssemblyReferences(Configuration solutionConfiguration) { // obtain project configuration (corresponding with solution configuration) ConfigurationBase config = Parent.BuildConfigurations[solutionConfiguration]; // ensure wrapper is actually created string assemblyFile = CreateWrapper(config); if (!File.Exists(assemblyFile)) { throw new BuildException(string.Format(CultureInfo.InvariantCulture, "Couldn't find assembly \"{0}\", referenced by project \"{1}\".", assemblyFile, Parent.Name), Location.UnknownLocation); } // add referenced assembly to list of reference assemblies StringCollection assemblyReferences = new StringCollection(); assemblyReferences.Add(assemblyFile); return(assemblyReferences); }
/// <summary> /// Returns a <see cref="ProcessStartInfo" /> for launching the compiler /// for this project. /// </summary> /// <param name="config">The configuration to build.</param> /// <param name="responseFile">The response file for the compiler.</param> /// <returns> /// A <see cref="ProcessStartInfo" /> for launching the compiler for /// this project. /// </returns> protected override ProcessStartInfo GetProcessStartInfo(ConfigurationBase config, string responseFile) { ProcessStartInfo psi = new ProcessStartInfo(FileUtils.CombinePaths(SolutionTask. Project.TargetFramework.FrameworkDirectory.FullName, "vjc.exe"), "@\"" + responseFile + "\""); // to resolve the path to the file specified in the AssemblyKeyFile // attribute, the command line compilers try to resolve that relative // path using the output directory and the current directory // // VS.NET compiles assembly to the intermediate output directory and // uses the solution directory as current directory if (SolutionTask.SolutionFile != null) { psi.WorkingDirectory = Path.GetDirectoryName(SolutionTask.SolutionFile.FullName); } else { psi.WorkingDirectory = ProjectDirectory.FullName; } return(psi); }
/// <summary> /// Gets the complete set of assemblies that need to be referenced when /// a project references this project. /// </summary> /// <param name="solutionConfiguration">The solution configuration that is built.</param> /// <returns> /// The complete set of assemblies that need to be referenced when a /// project references this project. /// </returns> /// <remarks> /// <para> /// Apparently, there's some hack in VB.NET that allows a type to be used /// that derives from a type in an assembly that is not referenced by the /// project. /// </para> /// <para> /// When building from the command line (using vbc), the following error /// is reported "error BC30007: Reference required to assembly 'X' /// containing the base class 'X'. Add one to your project". /// </para> /// <para> /// Somehow VB.NET can workaround this issue, without actually adding a /// reference to that assembly. I verified this with both VS.NET 2003 and /// VS.NET 2005. /// </para> /// <para> /// For now, we have no other option than to return all assembly /// references of the referenced project if the parent is a VB.NET /// project. /// </para> /// </remarks> public override StringCollection GetAssemblyReferences(Configuration solutionConfiguration) { StringCollection assemblyReferences = null; // check if parent is a VB.NET project if (typeof(VBProject).IsAssignableFrom(Parent.GetType())) { assemblyReferences = Project.GetAssemblyReferences(solutionConfiguration); } else { assemblyReferences = new StringCollection(); } ConfigurationBase projectConfig = Project.GetConfiguration( solutionConfiguration); // check if project is actual configured to be built if (projectConfig != null) { string projectOutputFile = projectConfig.BuildPath; // check if project has output file if (projectOutputFile != null) { if (File.Exists(projectOutputFile)) { // add primary output to list of reference assemblies assemblyReferences.Add(projectOutputFile); } } } // return assembly references return(assemblyReferences); }
protected virtual void SetProjectBuildConfiguration(ProjectEntry projectEntry) { if (projectEntry.BuildConfigurations == null) { // project was not loaded from solution file, and as a result // there's no project configuration section available, so we'll // consider all project configurations as valid build // configurations ProjectBase project = projectEntry.Project; project.BuildConfigurations.Clear(); foreach (ConfigurationDictionaryEntry ce in project.ProjectConfigurations) { project.BuildConfigurations[ce.Name] = ce.Config; } } else { // project was loaded from solution file, so only add build // configurations that were listed in project configuration // section ProjectBase project = projectEntry.Project; foreach (ConfigurationMapEntry ce in projectEntry.BuildConfigurations) { Configuration solutionConfig = ce.Key; Configuration projectConfig = ce.Value; ConfigurationBase conf = project.ProjectConfigurations [projectConfig]; if (conf != null) { project.BuildConfigurations[solutionConfig] = conf; } } } }
/// <summary> /// Returns a <see cref="ProcessStartInfo" /> for launching the compiler /// for this project. /// </summary> /// <param name="config">The configuration to build.</param> /// <param name="responseFile">The response file for the compiler.</param> /// <returns> /// A <see cref="ProcessStartInfo" /> for launching the compiler for /// this project. /// </returns> protected abstract ProcessStartInfo GetProcessStartInfo(ConfigurationBase config, string responseFile);
protected bool ExecuteBuildEvent(string buildEvent, string buildCommandLine, string batchFile, string workingDirectory, ConfigurationBase config) { // create the batch file using (StreamWriter sw = new StreamWriter(batchFile)) { sw.WriteLine("@echo off"); // replace any VS macros in the command line with real values buildCommandLine = config.ExpandMacros(buildCommandLine); // handle linebreak charaters buildCommandLine = buildCommandLine.Replace("
", "\n"); sw.WriteLine(buildCommandLine); sw.WriteLine("if errorlevel 1 goto EventReportError"); sw.WriteLine("goto EventEnd"); sw.WriteLine(":EventReportError"); sw.WriteLine("echo Project error: A tool returned an error code from the build event"); sw.WriteLine("exit 1"); sw.WriteLine(":EventEnd"); } // execute the batch file ProcessStartInfo psi = new ProcessStartInfo(batchFile); psi.UseShellExecute = false; psi.RedirectStandardOutput = true; // For logging psi.WorkingDirectory = workingDirectory; // start the process now Process batchEvent = Process.Start(psi); // keep logging output from the process for as long as it exists while (true) { string logContents = batchEvent.StandardOutput.ReadLine(); if (logContents == null) { break; } Log(Level.Verbose, " [" + buildEvent.ToLower(CultureInfo.InvariantCulture) + "] " + logContents); } batchEvent.WaitForExit(); // notify if there where problems running the batch file or it // returned errors int exitCode = batchEvent.ExitCode; if (exitCode == 0) { Log(Level.Verbose, "{0} succeeded (exit code = 0)", buildEvent); } else { Log(Level.Error, "{0} failed with exit code = {1}", buildEvent, exitCode); } return((exitCode == 0) ? true : false); }
/// <summary> /// Prepares the project for being built. /// </summary> /// <param name="solutionConfiguration">The solution configuration that is built.</param> /// <remarks> /// The default implementation will ensure that none of the output files /// are marked read-only. /// </remarks> protected virtual void Prepare(Configuration solutionConfiguration) { // determine the output files of the project Hashtable outputFiles = CollectionsUtil.CreateCaseInsensitiveHashtable(); GetOutputFiles(solutionConfiguration, outputFiles); // use the <attrib> task to ensure none of the output files are // marked read-only AttribTask attribTask = new AttribTask(); // parent is solution task attribTask.Parent = SolutionTask; // inherit project from solution task attribTask.Project = SolutionTask.Project; // inherit namespace manager from solution task attribTask.NamespaceManager = SolutionTask.NamespaceManager; // inherit verbose setting from solution task attribTask.Verbose = SolutionTask.Verbose; // only output warning messages or higher, unless // we're running in verbose mode if (!attribTask.Verbose) { attribTask.Threshold = Level.Warning; } // make sure framework specific information is set attribTask.InitializeTaskConfiguration(); // set parent of child elements attribTask.AttribFileSet.Parent = attribTask; // inherit project for child elements from containing task attribTask.AttribFileSet.Project = attribTask.Project; // inherit namespace manager from containing task attribTask.AttribFileSet.NamespaceManager = attribTask.NamespaceManager; // we want to reset the read-only attribute of all output files attribTask.ReadOnlyAttrib = false; // obtain project configuration (corresponding with solution configuration) ConfigurationBase config = BuildConfigurations[solutionConfiguration]; // add all output files to the <attrib> fileset foreach (DictionaryEntry de in outputFiles) { attribTask.AttribFileSet.Includes.Add(FileUtils.CombinePaths( config.OutputDir.FullName, (string)de.Value)); } // increment indentation level attribTask.Project.Indent(); try { // execute task attribTask.Execute(); } finally { // restore indentation level attribTask.Project.Unindent(); } }
protected bool ExecuteBuildEvent(string buildEvent, string buildCommandLine, string batchFile, string workingDirectory, ConfigurationBase config) { // create the batch file using (StreamWriter sw = new StreamWriter(batchFile)) { sw.WriteLine("@echo off"); // replace any VS macros in the command line with real values buildCommandLine = config.ExpandMacros(buildCommandLine); // handle linebreak charaters buildCommandLine = buildCommandLine.Replace("
", "\n"); sw.WriteLine(buildCommandLine); sw.WriteLine("if errorlevel 1 goto EventReportError"); sw.WriteLine("goto EventEnd"); sw.WriteLine(":EventReportError"); sw.WriteLine("echo Project error: A tool returned an error code from the build event"); sw.WriteLine("exit 1"); sw.WriteLine(":EventEnd"); } // execute the batch file ProcessStartInfo psi = new ProcessStartInfo(batchFile); psi.UseShellExecute = false; psi.RedirectStandardOutput = true; // For logging psi.WorkingDirectory = workingDirectory; // start the process now Process batchEvent = Process.Start(psi); // keep logging output from the process for as long as it exists while (true) { string logContents = batchEvent.StandardOutput.ReadLine(); if (logContents == null) { break; } Log(Level.Verbose, " [" + buildEvent.ToLower(CultureInfo.InvariantCulture) + "] " + logContents); } batchEvent.WaitForExit(); // notify if there where problems running the batch file or it // returned errors int exitCode = batchEvent.ExitCode; if (exitCode == 0) { Log(Level.Verbose, "{0} succeeded (exit code = 0)", buildEvent); } else { Log(Level.Error, "{0} failed with exit code = {1}", buildEvent, exitCode); } return (exitCode == 0) ? true : false; }
public void Add(Configuration key, ConfigurationBase value) { _innerHash.Add (key, value); }
public bool ContainsValue(ConfigurationBase value) { return(_innerHash.ContainsValue(value)); }
protected void GetDependenciesFromProjects(Configuration solutionConfiguration) { Log(Level.Verbose, "Gathering additional dependencies..."); // first get all of the output files foreach (ProjectEntry projectEntry in ProjectEntries) { ProjectBase project = projectEntry.Project; if (project == null) { // skip projects that are not loaded/supported continue; } foreach (ConfigurationBase projectConfig in project.ProjectConfigurations.Values) { string projectOutputFile = projectConfig.OutputPath; if (projectOutputFile != null) { _htOutputFiles[projectOutputFile] = project.Guid; } } } // if one of output files resides in reference search path - circle began // we must build project with that outputFile before projects referencing it // (similar to project dependency) VS.NET 7.0/7.1 do not address this problem // build list of output which reside in such folders Hashtable outputsInAssemblyFolders = CollectionsUtil.CreateCaseInsensitiveHashtable(); foreach (DictionaryEntry de in _htOutputFiles) { string outputfile = (string)de.Key; string folder = Path.GetDirectoryName(outputfile); if (_solutionTask.AssemblyFolderList.Contains(folder)) { outputsInAssemblyFolders[Path.GetFileName(outputfile)] = (string)de.Value; } } // build the dependency list foreach (ProjectEntry projectEntry in ProjectEntries) { ProjectBase project = projectEntry.Project; if (project == null) { // skip projects that are not loaded/supported continue; } // check if project actually supports the build configuration ConfigurationBase projectConfig = project.BuildConfigurations[solutionConfiguration]; if (projectConfig == null) { continue; } // ensure output directory exists. VS creates output directories // before it starts compiling projects if (!projectConfig.OutputDir.Exists) { projectConfig.OutputDir.Create(); projectConfig.OutputDir.Refresh(); } foreach (ReferenceBase reference in project.References) { ProjectReferenceBase projectReference = reference as ProjectReferenceBase; if (projectReference != null) { project.ProjectDependencies.Add(projectReference.Project); } else { string outputFile = reference.GetPrimaryOutputFile( solutionConfiguration); // if we reference an assembly in an AssemblyFolder // that is an output directory of another project, // then add dependency on that project if (outputFile == null) { continue; } string dependencyGuid = (string)outputsInAssemblyFolders[Path.GetFileName(outputFile)]; if (dependencyGuid == null) { continue; } ProjectEntry dependencyEntry = ProjectEntries[dependencyGuid]; if (dependencyEntry != null && dependencyEntry.Project != null) { project.ProjectDependencies.Add(dependencyEntry.Project); } } } } }
/// <summary> /// Removes wrapper assembly from build directory, if wrapper assembly /// no longer exists in output directory or is not in sync with build /// directory, to force rebuild. /// </summary> /// <param name="config">The project configuration.</param> private void Sync(ConfigurationBase config) { if (!CopyLocal || !File.Exists(WrapperAssembly)) { // nothing to synchronize return; } // determine path where wrapper assembly should be deployed to string outputFile = FileUtils.CombinePaths(config.OutputDir.FullName, Path.GetFileName(WrapperAssembly)); // determine last modification date/time of built wrapper assembly DateTime wrapperModTime = File.GetLastWriteTime(WrapperAssembly); // rebuild wrapper assembly if output assembly is more recent, // or have been removed (by the user) to force a rebuild if (FileSet.FindMoreRecentLastWriteTime(outputFile, wrapperModTime) != null) { // remove wrapper assembly to ensure a rebuild is performed DeleteTask deleteTask = new DeleteTask(); deleteTask.Project = SolutionTask.Project; deleteTask.Parent = SolutionTask; deleteTask.InitializeTaskConfiguration(); deleteTask.File = new FileInfo(WrapperAssembly); deleteTask.Threshold = Level.None; // no output in build log deleteTask.Execute(); } }
private string CreateWrapper(ConfigurationBase config) { // if wrapper assembly was created during the current build, then // there's no need to create it again if (IsCreated) { return WrapperAssembly; } // synchronize build and output directory Sync(config); switch (WrapperTool) { case "primary": // nothing to do for Primary Interop Assembly break; case "tlbimp": if (PrimaryInteropAssembly != null) { // if tlbimp is defined as import tool, but a primary // interop assembly is available, then output a // warning Log(Level.Warning, "The component \"{0}\", referenced by" + " project \"{1}\" has an updated custom wrapper" + " available.", Name, Parent.Name); } ImportTypeLibrary(); break; case "aximp": ImportActiveXLibrary(); break; default: throw new BuildException(string.Format(CultureInfo.InvariantCulture, "Wrapper tool \"{0}\" for reference \"{1}\" in project" + " \"{2}\" is not supported.", WrapperTool, Name, Parent.Name), Location.UnknownLocation); } // mark wrapper as completed _isCreated = true; return WrapperAssembly; }
internal ConfigurationDictionaryEntry(Configuration name, ConfigurationBase config) { _name = name; _config = config; }
public void Add(Configuration key, ConfigurationBase value) { _innerHash.Add(key, value); }
/// <summary> /// Converts assembly references to projects to project references, adding /// a build dependency.c /// </summary> /// <param name="project">The <see cref="ProjectBase" /> to analyze.</param> /// <param name="solutionConfiguration">The solution configuration that is built.</param> /// <param name="builtProjects"><see cref="Hashtable" /> containing list of projects that have been built.</param> /// <param name="failedProjects"><see cref="Hashtable" /> containing list of projects that failed to build.</param> protected bool FixProjectReferences(ProjectBase project, Configuration solutionConfiguration, Hashtable builtProjects, Hashtable failedProjects) { // check if the project still has dependencies that have not been // built if (HasDirtyProjectDependency(project, builtProjects)) { return(false); } ConfigurationBase projectConfig = project.BuildConfigurations[solutionConfiguration]; // check if the project actually supports the build configuration if (projectConfig == null) { return(false); } Log(Level.Verbose, "Fixing up references..."); ArrayList projectReferences = (ArrayList) project.References.Clone(); bool referencesFailedProject = false; foreach (ReferenceBase reference in projectReferences) { AssemblyReferenceBase assemblyReference = reference as AssemblyReferenceBase; if (assemblyReference == null) { // project references and wrappers don't // need to be fixed continue; } ProjectBase projectRef = null; string outputFile = assemblyReference.GetPrimaryOutputFile( solutionConfiguration); if (outputFile == null) { continue; } if (_htOutputFiles.Contains(outputFile)) { // if the reference is an output file of // another build configuration of a project // and this output file wasn't built before // then use the output file for the current // build configuration // // eg. a project file might be referencing the // the debug assembly of a given project as an // assembly reference, but the projects are now // being built in release configuration, so // instead of failing the build we use the // release assembly of that project // Note that this was designed to intentionally // deviate from VS.NET's building strategy. // See "Reference Configuration Matching" at http://nant.sourceforge.net/wiki/index.php/SolutionTask // for why we must always convert file references to project references // If we want a different behaviour, this // should be controlled by a flag projectRef = ProjectEntries[(string)_htOutputFiles[outputFile]].Project; } else if (_outputDir != null) { // if an output directory is set, then the // assembly reference might not have been // resolved during Reference initialization, // as the output file of the project might // not have existed at that time // // this will perform matching on file name // only, so its really tricky (VS.NET does // not support this) string projectOutput = FileUtils.CombinePaths( _outputDir.FullName, Path.GetFileName( outputFile)); if (_htOutputFiles.Contains(projectOutput)) { projectRef = (ProjectBase)ProjectEntries[ (string)_htOutputFiles[projectOutput]].Project; } } // try matching assembly reference and project on assembly name // if the assembly file does not exist if (projectRef == null && !System.IO.File.Exists(outputFile)) { foreach (ProjectEntry projectEntry in ProjectEntries) { // we can only do this for managed projects, as we only have // an assembly name for these ManagedProjectBase managedProject = projectEntry.Project as ManagedProjectBase; if (managedProject == null) { continue; } // check if the assembly names match if (assemblyReference.Name == managedProject.ProjectSettings.AssemblyName) { projectRef = managedProject; break; } } } if (projectRef != null) { if (!referencesFailedProject && failedProjects.ContainsKey(projectRef.Guid)) { referencesFailedProject = true; } ProjectReferenceBase projectReference = assemblyReference. CreateProjectReference(projectRef); Log(Level.Verbose, "Converted assembly reference to project reference: {0} -> {1}", assemblyReference.Name, projectReference.Name); // remove assembly reference from project project.References.Remove(assemblyReference); // add project reference instead project.References.Add(projectReference); // unless referenced project has already been build, add // referenced project as project dependency if (!builtProjects.Contains(projectReference.Project.Guid)) { project.ProjectDependencies.Add(projectReference.Project); } } } return(referencesFailedProject); }
private bool ExecuteBuildEvent(string buildEvent, string buildCommandLine, ConfigurationBase config) { string batchFile = null; try { // get unique temp file name to write command line of build event to batchFile = Path.GetTempFileName(); // remove temp file LongPathFile.Delete(batchFile); // change extension to .bat batchFile = Path.ChangeExtension(batchFile, ".bat"); // execute the build event return base.ExecuteBuildEvent(buildEvent, buildCommandLine, batchFile, ProjectDirectory.FullName, config); } finally { if (batchFile != null && File.Exists(batchFile)) { LongPathFile.Delete(batchFile); } } }
/// <summary> /// Returns a <see cref="ProcessStartInfo" /> for launching the compiler /// for this project. /// </summary> /// <param name="config">The configuration to build.</param> /// <param name="responseFile">The response file for the compiler.</param> /// <returns> /// A <see cref="ProcessStartInfo" /> for launching the compiler for /// this project. /// </returns> protected override ProcessStartInfo GetProcessStartInfo(ConfigurationBase config, string responseFile) { ProcessStartInfo psi = new ProcessStartInfo(FileUtils.CombinePaths(SolutionTask. Project.TargetFramework.FrameworkDirectory.FullName, "vjc.exe"), "@\"" + responseFile + "\""); // to resolve the path to the file specified in the AssemblyKeyFile // attribute, the command line compilers try to resolve that relative // path using the output directory and the current directory // // VS.NET compiles assembly to the intermediate output directory and // uses the solution directory as current directory if (SolutionTask.SolutionFile != null) { psi.WorkingDirectory = Path.GetDirectoryName(SolutionTask.SolutionFile.FullName); } else { psi.WorkingDirectory = ProjectDirectory.FullName; } return psi; }
public bool ContainsValue(ConfigurationBase value) { return _innerHash.ContainsValue(value); }
/// <summary> /// Returns a <see cref="ProcessStartInfo" /> for launching the compiler /// for this project. /// </summary> /// <param name="config">The configuration to build.</param> /// <param name="responseFile">The response file for the compiler.</param> /// <returns> /// A <see cref="ProcessStartInfo" /> for launching the compiler for /// this project. /// </returns> protected override ProcessStartInfo GetProcessStartInfo(ConfigurationBase config, string responseFile) { ProcessStartInfo psi = new ProcessStartInfo(FileUtils.CombinePaths(SolutionTask. Project.TargetFramework.FrameworkDirectory.FullName, "vbc.exe"), "@\"" + responseFile + "\""); // Visual Basic.NET uses the directory from which VS.NET // was launched as working directory, the closest match // and best behaviour for us is to use the <solution dir> // as working directory and fallback to the <project dir> // if the project was explicitly specified if (SolutionTask.SolutionFile != null) { psi.WorkingDirectory = SolutionTask.SolutionFile.DirectoryName; } else { psi.WorkingDirectory = ProjectDirectory.FullName; } return psi; }