using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using Microsoft.Win32;
using Cosmos.Build.Installer;
namespace Cosmos.Build.Builder
{
///
/// Cosmos task.
///
///
public class CosmosTask : Task
{
private string mCosmosDir;
private string mOutputDir;
private BuildState mBuildState;
private string mAppDataDir;
private int mReleaseNo;
private string mInnoFile;
private string mInnoPath;
private List mExceptionList = new List();
public CosmosTask(string aCosmosDir, int aReleaseNo)
{
mCosmosDir = aCosmosDir;
mAppDataDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Cosmos User Kit");
mReleaseNo = aReleaseNo;
mInnoFile = Path.Combine(mCosmosDir, @"Setup\Cosmos.iss");
}
///
/// Get name of the setup file based on release number and the current setting.
///
/// Release number for the current setup.
/// Name of the setup file.
public static string GetSetupName(int releaseNumber)
{
string setupName = $"CosmosUserKit-{releaseNumber}-vs2017";
if (App.UseVsHive)
{
setupName += "Exp";
}
return setupName;
}
private void CleanupVSIPFolder()
{
if (Directory.Exists(mOutputDir))
{
Section("Cleaning up VSIP directory");
Echo($" {mOutputDir}");
Directory.Delete(mOutputDir, true);
}
}
public void CleanupAlreadyInstalled()
{
if (Directory.Exists(mAppDataDir))
{
Section("Cleaning up UserKit directory");
Echo(" " + mAppDataDir);
Directory.Delete(mAppDataDir, true);
}
}
protected override List DoRun()
{
mOutputDir = Path.Combine(mCosmosDir, @"Build\VSIP");
CheckPrereqs();
if (mBuildState != BuildState.PrerequisiteMissing)
{
CleanupVSIPFolder();
CompileCosmos();
CreateSetup();
if (!App.IsUserKit)
{
CleanupAlreadyInstalled();
RunSetup();
WriteDevKit();
if (!App.DoNotLaunchVS)
{
LaunchVS();
}
}
Done();
}
return mExceptionList;
}
protected void MSBuild(string aSlnFile, string aBuildCfg)
{
string xMSBuild = Path.Combine(Paths.VSPath, "MSBuild", "15.0", "Bin", "msbuild.exe");
string xParams = $"{Quoted(aSlnFile)} " +
"/nologo " +
"/maxcpucount " +
"/nodeReuse:False " +
$"/p:Configuration={Quoted(aBuildCfg)} " +
$"/p:Platform={Quoted("Any CPU")} " +
$"/p:OutputPath={Quoted(mOutputDir)}";
if (!App.NoMSBuildClean)
{
StartConsole(xMSBuild, $"/t:Clean {xParams}");
}
StartConsole(xMSBuild, $"/t:Build {xParams}");
}
protected int NumProcessesContainingName(string name)
{
return (from x in Process.GetProcesses() where x.ProcessName.Contains(name) select x).Count();
}
protected void CheckIfBuilderRunning()
{
//Check for builder process
Echo("Checking if Builder is already running.");
// Check > 1 so we exclude ourself.
if (NumProcessesContainingName("Cosmos.Build.Builder") > 1)
{
throw new Exception("Another instance of builder is running.");
}
}
protected void CheckIfUserKitRunning()
{
Echo("Check if User Kit Installer is already running.");
if (NumProcessesContainingName("CosmosUserKit") > 0)
{
throw new Exception("Another instance of the user kit installer is running.");
}
}
protected void CheckIfVSRunning()
{
int xSeconds = 500;
if (Debugger.IsAttached)
{
Echo("Checking if Visual Studio is running is ignored by debugging of Builder.");
}
else
{
Echo("Checking if Visual Studio is running.");
if (IsRunning("devenv"))
{
Echo("--Visual Studio is running.");
Echo("--Waiting " + xSeconds + " seconds to see if Visual Studio exits.");
// VS doesnt exit right away and user can try devkit again after VS window has closed but is still running.
// So we wait a few seconds first.
if (WaitForExit("devenv", xSeconds * 1000))
{
throw new Exception("Visual Studio is running. Please close it or kill it in task manager.");
}
}
}
}
protected void NotFound(string aName)
{
mExceptionList.Add("Prerequisite '" + aName + "' not found.");
mBuildState = BuildState.PrerequisiteMissing;
}
protected void CheckPrereqs()
{
Section("Checking Prerequisites");
CheckIfUserKitRunning();
CheckIfVSRunning();
CheckIfBuilderRunning();
CheckForNetCore();
CheckForVisualStudioExtensionTools();
CheckForInno();
}
private void CheckForInno()
{
Echo("Checking for Inno Setup");
using (var xKey = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Inno Setup 5_is1", false))
{
if (xKey == null)
{
mExceptionList.Add("Cannot find Inno Setup.");
mBuildState = BuildState.PrerequisiteMissing;
return;
}
mInnoPath = (string) xKey.GetValue("InstallLocation");
if (string.IsNullOrWhiteSpace(mInnoPath))
{
mExceptionList.Add("Cannot find Inno Setup.");
mBuildState = BuildState.PrerequisiteMissing;
return;
}
}
Echo("Checking for Inno Preprocessor");
if (!File.Exists(Path.Combine(mInnoPath, "ISPP.dll")))
{
mExceptionList.Add("Inno Preprocessor not detected.");
mBuildState = BuildState.PrerequisiteMissing;
return;
}
}
private void CheckForNetCore()
{
Echo("Checking for .NET Core");
if (!Paths.VSInstancePackages.Contains("Microsoft.VisualStudio.Workload.NetCoreTools"))
{
mExceptionList.Add(".NET Core not detected.");
mBuildState = BuildState.PrerequisiteMissing;
}
}
private void CheckForVisualStudioExtensionTools()
{
Echo("Checking for Visual Studio Extension Tools");
if (!Paths.VSInstancePackages.Contains("Microsoft.VisualStudio.Workload.VisualStudioExtension"))
{
mExceptionList.Add("Visual Studio Extension tools not detected.");
mBuildState = BuildState.PrerequisiteMissing;
}
}
private void WriteDevKit()
{
Section("Writing Dev Kit to Registry");
// Inno deletes this from registry, so we must add this after.
// We let Inno delete it, so if user runs it by itself they get
// only UserKit, and no DevKit settings.
// HKCU instead of HKLM because builder does not run as admin.
//
// HKCU is not redirected.
using (var xKey = Registry.CurrentUser.CreateSubKey(@"Software\Cosmos"))
{
xKey.SetValue("DevKit", mCosmosDir);
}
}
private void Restore(string project)
{
string xNuget = Path.Combine(mCosmosDir, "Build", "Tools", "nuget.exe");
string xRestoreParams = $"restore {Quoted(project)}";
string xUpdateParams = $"update -self";
StartConsole(xNuget, xUpdateParams);
StartConsole(xNuget, xRestoreParams);
}
private void Pack(string project, string destDir, string version)
{
string xMSBuild = Path.Combine(Paths.VSPath, "MSBuild", "15.0", "Bin", "msbuild.exe");
string xParams = $"{Quoted(project)} /nodeReuse:False /t:Restore;Pack /maxcpucount /p:PackageVersion={Quoted(version)} /p:PackageOutputPath={Quoted(destDir)}";
StartConsole(xMSBuild, xParams);
}
private void Publish(string project, string destDir)
{
string xMSBuild = Path.Combine(Paths.VSPath, "MSBuild", "15.0", "Bin", "msbuild.exe");
string xParams = $"{Quoted(project)} /nodeReuse:False /t:Publish /maxcpucount /p:RuntimeIdentifier=win7-x86 /p:PublishDir={Quoted(destDir)}";
StartConsole(xMSBuild, xParams);
}
private void CompileCosmos()
{
string xVSIPDir = Path.Combine(mCosmosDir, "Build", "VSIP");
string xPackagesDir = Path.Combine(xVSIPDir, "KernelPackages");
string xVersion = "1.0.1";
if (!App.IsUserKit)
{
xVersion += "-" + DateTime.Now.ToString("yyyyMMddHHmm");
}
if (!Directory.Exists(xVSIPDir))
{
Directory.CreateDirectory(xVSIPDir);
}
Section("Restoring Nuget Packages");
Restore(Path.Combine(mCosmosDir, @"Cosmos.sln"));
Section("Compiling Cosmos");
MSBuild(Path.Combine(mCosmosDir, @"Build.sln"), "Debug");
Section("Compiling Tools");
Publish(Path.Combine(mCosmosDir, "source", "Cosmos.Build.MSBuild"), Path.Combine(xVSIPDir, "MSBuild"));
Publish(Path.Combine(mCosmosDir, "source", "IL2CPU"), Path.Combine(xVSIPDir, "IL2CPU"));
Publish(Path.Combine(mCosmosDir, "source", "XSharp.Compiler"), Path.Combine(xVSIPDir, "XSharp"));
Publish(Path.Combine(mCosmosDir, "Tools", "NASM"), Path.Combine(xVSIPDir, "NASM"));
Section("Compiling Kernel Packages");
Pack(Path.Combine(mCosmosDir, "source", "Cosmos.Common"), xPackagesDir, xVersion);
Pack(Path.Combine(mCosmosDir, "source", "Cosmos.Core"), xPackagesDir, xVersion);
Pack(Path.Combine(mCosmosDir, "source", "Cosmos.Core.Common"), xPackagesDir, xVersion);
Pack(Path.Combine(mCosmosDir, "source", "Cosmos.Core.Memory"), xPackagesDir, xVersion);
Pack(Path.Combine(mCosmosDir, "source", "Cosmos.Core.Plugs"), xPackagesDir, xVersion);
Pack(Path.Combine(mCosmosDir, "source", "Cosmos.Core.Plugs.Asm"), xPackagesDir, xVersion);
Pack(Path.Combine(mCosmosDir, "source", "Cosmos.Debug.Kernel"), xPackagesDir, xVersion);
Pack(Path.Combine(mCosmosDir, "source", "Cosmos.Debug.Kernel.Plugs.Asm"), xPackagesDir, xVersion);
Pack(Path.Combine(mCosmosDir, "source", "Cosmos.HAL"), xPackagesDir, xVersion);
Pack(Path.Combine(mCosmosDir, "source", "Cosmos.IL2CPU.Plugs"), xPackagesDir, xVersion);
Pack(Path.Combine(mCosmosDir, "source", "Cosmos.System"), xPackagesDir, xVersion);
Pack(Path.Combine(mCosmosDir, "source", "Cosmos.System.Plugs"), xPackagesDir, xVersion);
}
private void CopyTemplates()
{
Section("Copying Templates");
CD(mOutputDir);
SrcPath = Path.Combine(mCosmosDir, @"source\Cosmos.VS.Package\obj\Debug");
Copy("CosmosProject (C#).zip", true);
Copy("CosmosKernel (C#).zip", true);
Copy("CosmosProject (F#).zip", true);
Copy("Cosmos.zip", true);
Copy("CosmosProject (VB).zip", true);
Copy("CosmosKernel (VB).zip", true);
Copy(mCosmosDir + @"source\XSharp.VS\Template\XSharpFileItem.zip", true);
}
private void CreateSetup()
{
Section("Creating Setup");
string xISCC = Path.Combine(mInnoPath, "ISCC.exe");
if (!File.Exists(xISCC))
{
mExceptionList.Add("Cannot find Inno setup.");
return;
}
string xCfg = App.IsUserKit ? "UserKit" : "DevKit";
string vsVersionConfiguration = "vs2017";
// Use configuration which will instal to the VS Exp Hive
if (App.UseVsHive)
{
vsVersionConfiguration += "Exp";
}
Echo($" {xISCC} /Q {Quoted(mInnoFile)} /dBuildConfiguration={xCfg} /dVSVersion={vsVersionConfiguration} /dVSPath={Quoted(Paths.VSPath)} /dChangeSetVersion={Quoted(mReleaseNo.ToString())}");
StartConsole(xISCC, $"/Q {Quoted(mInnoFile)} /dBuildConfiguration={xCfg} /dVSVersion={vsVersionConfiguration} /dVSPath={Quoted(Paths.VSPath)} /dChangeSetVersion={Quoted(mReleaseNo.ToString())}");
}
private void LaunchVS()
{
Section("Launching Visual Studio");
string xVisualStudio = Path.Combine(Paths.VSPath, "Common7", "IDE", "devenv.exe");
if (!File.Exists(xVisualStudio))
{
mExceptionList.Add("Cannot find Visual Studio.");
return;
}
if (App.ResetHive)
{
Echo("Resetting hive");
Start(xVisualStudio, @"/setup /rootsuffix Exp /ranu");
}
Echo("Launching Visual Studio");
Start(xVisualStudio, Quoted(mCosmosDir + @"Cosmos.sln"), false, true);
}
private void RunSetup()
{
Section("Running Setup");
KillProcesses("dotnet");
KillProcesses("msbuild");
string setupName = GetSetupName(mReleaseNo);
if (App.UseTask)
{
// This is a hack to avoid the UAC dialog on every run which can be very disturbing if you run
// the dev kit a lot.
Start(@"schtasks.exe", @"/run /tn " + Quoted("CosmosSetup"), true, false);
// Must check for start before stop, else on slow machines we exit quickly because Exit is found before
// it starts.
// Some slow user PCs take around 5 seconds to start up the task...
int xSeconds = 10;
var xTimed = DateTime.Now;
Echo("Waiting " + xSeconds + " seconds for Setup to start.");
if (WaitForStart(setupName, xSeconds * 1000))
{
mExceptionList.Add("Setup did not start.");
return;
}
Echo("Setup is running. " + DateTime.Now.Subtract(xTimed).ToString(@"ss\.fff"));
// Scheduler starts it an exits, but we need to wait for the setup itself to exit before proceding
Echo("Waiting for Setup to complete.");
WaitForExit(setupName);
}
else
{
Start(mCosmosDir + @"Setup\Output\" + setupName + ".exe", @"/SILENT");
}
}
private void Done()
{
Section("Build Complete!");
}
}
}