///////////////////////////////////////////////////////////////////////////////
//
//  Microsoft Research Singularity
//
//  Copyright (c) Microsoft Corporation.  All rights reserved.
//
//  File:   nib.cs
//
//  Note:   Native image building tool.
//

using System;
using System.Collections;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.IO;
using System.Text;
using System.Threading;

#if !SINGULARITY
using System.Xml;
using System.Text.RegularExpressions;
using Windows;
#else
using Microsoft.Singularity.Xml;
using Microsoft.Singularity.Channels;
using Microsoft.Singularity.Io;
using Microsoft.Singularity.Directory;
using Microsoft.Singularity;
using Microsoft.Singularity.Applications;
#endif

namespace Microsoft.Singularity.Applications
{
    public class NativeImageBuilder
    {
        //
        // Global flags.
        //
#if !SINGULARITY
        public static string Bartok        = "bartok.exe";
        public static string LinkExecutablePath = "link.exe";
#else
        public static string Bartok        = "bartok";
        public static string LinkExecutablePath = "link";
#endif
        public static bool   DoClean       = false;
        public static bool   DoCodegen     = true;
        public static bool   DoLinker      = true;
        public static bool   DoManifest    = true;
        public static bool   DoLogBartok   = false;
        public static bool   ForceCodegen  = false;
        public static bool   ForceLinker   = false;
        public static bool   ForceManifest = false;
        public static bool   Verbose       = false;
        public static DateTime  Began;


        //
        // Fields in the native image building class
        //
        private Options options;
        // private string currentFile; // For warning and error messages.
        private static string architecture;
        private string cacheDirectory;
        private string libCacheDirectory;
        private string nativeDirectory;
        private string tempDirectory;
        private string optionsFile;

#if !SINGULARITY
        private XmlDocument optManifest;
        private XmlDocument appManifest;
#else
        private XmlNode optManifest;
        private XmlNode appManifest;
#endif

        // set up an object that can build native images based on the system manifest.
        public NativeImageBuilder(string cacheDirectory,
                                  string libCacheDirectory,
                                  string nativeDirectory,
                                  string tempDirectory)
        {
            this.options = null;
            this.cacheDirectory = cacheDirectory;
            this.libCacheDirectory = libCacheDirectory;
            this.nativeDirectory = nativeDirectory;
            this.tempDirectory = tempDirectory;
        }

        public bool LoadOptionsManifest(string optionsFile)
        {
            this.optionsFile = optionsFile;
            this.options = ParseOptionsManifest(optionsFile, out optManifest);
            return (options != null);
        }

        //////////////////////////////////////////////// Methods to help with XML.
        //
        private XmlNode AddElement(XmlNode parent, string name)
        {
#if !SINGULARITY
            XmlNode element = appManifest.CreateNode(XmlNodeType.Element, name, "");
            if (parent != null) {
                parent.AppendChild(element);
            }
            return element;
#else
            if (appManifest == null) {
                throw new Exception("appManifest is Null!");
            }
            XmlNode element = appManifest.CreateNode(name);
            if (parent != null) {
                parent.AddChild(element);
            }
            return element;

#endif
        }

        private void AddAttribute(XmlNode node, string name, string value)
        {
#if !SINGULARITY
            XmlAttribute attr = appManifest.CreateAttribute(name);
            attr.Value = value;
            node.Attributes.Append(attr);
#else
            node.AddAttribute(name,value);
#endif
        }

        private void InsertAttribute(XmlNode node, string name, string value)
        {
#if !SINGULARITY
            XmlAttribute attr = appManifest.CreateAttribute(name);
            attr.Value = value;
            node.Attributes.Prepend(attr);
#else
            node.PrependAttribute(name,value);
#endif
        }

        // Return an array with every child of the parent whose name matches
        // <name>
        private static XmlNode[] GetChildren(XmlNode parent, string name)
        {
            ArrayList result = new ArrayList();

            foreach (XmlNode child in parent.ChildNodes) {
                if (child.Name == name) {
                    result.Add(child);
                }
            }

            if (result.Count == 0) {
                return new XmlNode[0];
            }

            XmlNode[] children = new XmlNode [result.Count];
            for (int i = 0; i < result.Count; i++) {
                children[i] = (XmlNode)result[i];
            }
            return children;

        }

        // Get the first child named `name'
        private static XmlNode GetChild(XmlNode parent, string name)
        {
#if !SINGULARITY
            return parent[name];
#else
            if (parent == null) {
                throw new Exception("Null parent");
            }
            return parent.GetChild(name);
#endif
        }

        // Get end node along a path of first matches
        private static XmlNode GetDescendant(XmlNode root, string [] path)
        {
            XmlNode parent = root;

            foreach (string pathelement in path) {
                parent = GetChild(parent, pathelement);
                if (parent == null) {
                    return null;
                }
            }
            return parent;
        }

        // Get the named attribute if it exists.
        private static string GetAttribute(XmlNode node, string attrib)
        {
#if !SINGULARITY
            XmlAttribute xa = node.Attributes[attrib];
            return xa != null ? xa.Value : null;
#else
            if (node == null) {
                throw new Exception("Null node");
            }
            return node.GetAttribute(attrib, null);
#endif
        }

        private static string GetAttribute(XmlNode node, string attrib, string value)
        {
#if !SINGULARITY
            XmlAttribute xa = node.Attributes[attrib];
            return xa != null ? xa.Value : value;
#else
            if (node == null) {
                throw new Exception("Null node");
            }
            string val = node.GetAttribute(attrib,null);
            return  val != null ? val : value;
#endif
        }

        private static int GetAttribute(XmlNode node, string attrib, int value)
        {
#if !SINGULARITY
            XmlAttribute xa = node.Attributes[attrib];
            return xa != null ? Int32.Parse(xa.Value) : value;
#else
            if (node == null) {
                throw new Exception("Null node");
            }
            string val = node.GetAttribute(attrib,null);
            return  val != null ? Int32.Parse(val) : value;
#endif
        }


#if SINGULARITY

        public bool RunProgram(string pass, string appname, string program, string arguments, StreamWriter log, PipeMultiplexer! outputMux)
        {
            string commandName;
            string[]! args;
            Process p = null;

            UnicodePipeContract.Imp! childStdInImp;
            UnicodePipeContract.Exp! childStdInExp;
            UnicodePipeContract.Imp! childStdOutImp;
            UnicodePipeContract.Exp! childStdOutExp;

            NibHelper.BreakCommandLine(program + " " + arguments, new char[] {' '},
                         out commandName, out args);

            DirectoryServiceContract.Imp:Ready! ds = NibHelper.GetDirectoryServiceContract();
            double elapsed_ms = (DateTime.Now - Began).TotalMilliseconds;
            WriteLine("     {0,8:f3} [{1,-30}] {2}",
                      elapsed_ms / 1000,
                      appname,
                      pass);
            int exit = -1;
            try {
                if (log != null) {
                    UnicodePipeContract.NewChannel(out childStdInImp, out childStdInExp);
                    UnicodePipeContract.NewChannel(out childStdOutImp, out childStdOutExp);

                    p = Binder.CreateProcess(ds,args,(!)childStdInExp,(!)childStdOutImp);
                    if (p == null) {
                        delete childStdInImp;
                        delete childStdOutExp;
                        return false;
                    }
                    p.Start();

                    bool done = false;
                    assert childStdOutExp != null;
                    char[] buffer = null;
                    while (!done) {
                        switch receive {
                        case childStdOutExp.Write(i_buffer,i_offset, i_count):
                             //copy buffer;
                            buffer =  new char[i_count];
                            int i=0;
                            int j = i_offset;
                            for (; i < i_count;) {
                                buffer[i++] = i_buffer[j++];
                            }
                            childStdOutExp.SendAckWrite(i_buffer);
                            Console.WriteLine(buffer);
                            log.Write(buffer, 0, i_count);
                            if (i_count < buffer.Length) {
                                break;
                            }
                            buffer = null;
                            continue;
                        case childStdOutExp.ChannelClosed():
                            done = true;
                            continue;
                        }
                    }
                    try {
                        delete childStdInImp;
                        delete childStdOutExp;
                    }
                    catch (Exception ex) {
                        Console.WriteLine("error deleting endpoint");
                        ShowException(ex);
                    }
                }
                else {
                    p = Binder.CreateProcess(ds,args,outputMux);
                    if (p == null) {
                        return false;
                    }
                    p.Start();
                }
                if (p != null) {
                    p.Join();
                    exit = p.ExitCode;
                }
            }
            finally {
                NibHelper.ReleaseDirectoryServiceContract(ds);
            }
#if TODO
            if (log != null) {
                log.WriteLine("# peak memory: paged={0}KB, virtual={1}KB, working={2}KB",
                              peakPagedMemorySize / 1024,
                              peakWorkingSet / 1024,
                              peakVirtualMemorySize / 1024);
            }
#endif
            if (exit != 0) {
                if (true) {
                    WriteLine("nib: error [{0}] {1} failed with exit code: {2}", appname, program, exit);
                }
                return false;
            }
            else {
                return true;
            }
        }
#else
        public bool RunProgram(string pass, string appname, string program, string arguments, StreamWriter log)
        {
            Process p = new Process();
            p.StartInfo.UseShellExecute = false;
            p.StartInfo.RedirectStandardOutput = (log != null);
            p.StartInfo.RedirectStandardError = (log != null);
            p.StartInfo.FileName = program;
            p.StartInfo.Arguments = arguments;
            int exit = -1;

            long peakPagedMemorySize = 0;
            long peakVirtualMemorySize = 0;
            long peakWorkingSet = 0;
            try {
                p.Start();
                p.PriorityClass = ProcessPriorityClass.BelowNormal;

                // In Win32, the usual sequence is to create a process with the initial thread
                // suspended, then add the process to a job object.  But the .Net framework
                // doesn't provide any way to do this; the process is not created until
                // Process.Create(), and there is no flag in ProcessStartInfo to indicate that
                // the initial thread should be created suspended.  So, we just start the
                // process and immediately try to set the relevant parameters.

                if (_BuildingParallel) {
                    if (_JobHandle != IntPtr.Zero) {
                        if (!Kernel32.AssignProcessToJobObject(_JobHandle, p.Handle))
                            Console.WriteLine("FAILED to assign process to job: " + Kernel32.GetLastErrorText());
                    }

                    try {
                        p.ProcessorAffinity = _ChildProcessorAffinityMask;
                    }
                    catch (Exception ex) {
                        Console.WriteLine("FAILED to set processor affinity of child process!");
                        ShowException(ex);
                    }
                }

#if false
                double elapsed_ms = (DateTime.Now - Began).TotalMilliseconds;
                WriteLine("     {0,8:f3} [{1,-30}] {2}",
                          elapsed_ms / 1000, appname, pass);
#endif

                if (Verbose) {
                    WriteLine("        {0}", arguments);
                }
                if (log != null) {
                    Thread stderrCopier = new Thread(new ThreadStart(
                        delegate { CopyStreamToLog(p.StandardError, log); }));

                    stderrCopier.Start();
                    CopyStreamToLog(p.StandardOutput, log);
                    stderrCopier.Join();
                }
                p.WaitForExit();
                exit = p.ExitCode;
                try {
                    peakPagedMemorySize
                        = Math.Max(p.PeakPagedMemorySize64, peakPagedMemorySize);
                    peakVirtualMemorySize
                        = Math.Max(p.PeakVirtualMemorySize64, peakVirtualMemorySize);
                    peakWorkingSet
                        = Math.Max(p.PeakWorkingSet64, peakWorkingSet);
                }
                catch (Exception) {
                }
            }
            catch (Exception e) {
                Console.WriteLine("nib: Exception: {0}", e);
            }
            if (log != null) {
                log.WriteLine("# peak memory: paged={0}KB, virtual={1}KB, working={2}KB",
                              peakPagedMemorySize / 1024,
                              peakWorkingSet / 1024,
                              peakVirtualMemorySize / 1024);
            }

            if (exit != 0) {
                if (true) {
                    WriteLine("nib: error [{0}] {1} failed with exit code: {2}", appname, program, exit);
                    uint facility = ((uint) exit) >> 16;
                    uint failureType = ((uint) exit) & 0xFFFF;
                    if (facility == 0x8013) {
                        WriteLine("This is a CLR-related failure with code 0x{0:X}.", failureType);
                        WriteLine("See CorError.h in the .NET or Platform SDK for CLR error codes");
                    }
                }
                return false;
            }
            else {
                return true;
            }
        }

        void CopyStreamToLog(StreamReader reader, StreamWriter log) {
            for (;;) {
                string line = reader.ReadLine();
                if (line == null) {
                    break;
                }
                if (ErrorRegex.IsMatch(line)) {
                    Console.Error.WriteLine(line);
                }
                lock (log) {
                    log.WriteLine(line);
                }
            }
        }

        static readonly Regex ErrorRegex = new Regex(
            @"^ \s* (internal \s+ compiler \s+ )? error \s* : ",
            RegexOptions.IgnoreCase |
            RegexOptions.IgnorePatternWhitespace);
#endif

        //////////////////////////////////////////////////////////////////////////
        //
        class OAssembly
        {
            public readonly string Name;
            public readonly int MajorVersion;   // -1 if null.
            public readonly int MinorVersion;   // -1 if null.
            public readonly int Build;          // -1 if null.
            public readonly int Revision;       // -1 if null.
            public string Locale;
            public string PublicKey;
            public string Cache;
            // products:
            public string Final;

            public OAssembly(string name, string version)
            {
                this.Name = name;
                this.MajorVersion = -1;
                this.MinorVersion = -1;
                this.Build = -1;
                this.Revision = -1;

                if (version != null) {
                    int offset = 0;
                    this.MajorVersion = ParseInt(version, ref offset);
                    this.MinorVersion = ParseInt(version, ref offset);
                    this.Build = ParseInt(version, ref offset);
                    this.Revision = ParseInt(version, ref offset);
                }
            }

            private static int ParseInt(string s, ref int offset)
            {
                int num = -1;

                while (offset < s.Length && s[offset] >= '0' && s[offset] <= '9') {
                    if (num == -1) {
                        num = (s[offset++] - '0');
                    }
                    else {
                        num = num * 10 + (s[offset++] - '0');
                    }
                }
                if (offset < s.Length && s[offset] == '.') {
                    offset++;
                }
                return num;
            }

#if xxx
            public override String ToString()
            {
                return String.Format("{0}.{1}.{2}.{3}.{4}.{5}.{6}",
                                     Name,
                                     MajorVersion,
                                     MinorVersion,
                                     Build,
                                     Revision,
                                     Locale != null ? Locale : "",
                                     PublicKey != null ? PublicKey.Substring(0,8) : "");
            }
#endif
            // Only non-null fields in the target should be compared.
            // So that a 1.0.3000.3 in "this" matches 1.0.3000 in target.
            public bool IsMatch(OAssembly target)
            {
                if (String.Compare(Name, target.Name, true) != 0) {
                    return false;
                }
                if (target.Locale != null) {
                    if (Locale == null || String.Compare(Locale, target.Locale, true) != 0) {
                        return false;
                    }
                }
                if (target.PublicKey != null) {
                    if (PublicKey == null ||
                        String.Compare(PublicKey, target.PublicKey, true) != 0) {
                        return false;
                    }
                }
                if (target.MajorVersion != -1 && MajorVersion != target.MajorVersion) {
                    return false;
                }
                if (target.MinorVersion != -1 && MinorVersion != target.MinorVersion) {
                    return false;
                }
                if (target.Build != -1 && Build != target.Build) {
                    return false;
                }
                if (target.Revision != -1 && Revision != target.Revision) {
                    return false;
                }
                return true;
            }
        }

        class OReplacement : OAssembly
        {
            public OReplacement(string name, string version)
                : base(name, version)
            {
            }

            public OAssembly[] replacements;
        }

        class OCodegen
        {
            public string[] parameters;
        }

        class OLibrary
        {
            public readonly string Name;
            public string Cache;
            // products:
            public string Final;

            public OLibrary(string name)
            {
                this.Name = name;
            }
        }

        class OLinker
        {
            public string[] parameters;
            public OLibrary[] libraries;
        }

        class Options
        {
            public OReplacement[] assemblies;
            public OCodegen codegen;
            public OLinker linker;
        }

        //////////////////////////////////////////////////////////////////////////
        //
#if !SINGULARITY
        private Options ParseOptionsManifest(string optionsFile,
                                                    out XmlDocument optManifest)
#else
        private Options ParseOptionsManifest(string optionsFile,
                                                    out XmlNode optManifest)
#endif
        {
            // <options>
            //   <assembly name= version= publickey= locale= />
            //     <replacement name= cache= version= publickey= locale= />
            //   </assembly>
            //   <codegen>
            //     <parameter value= />
            //   </codegen>
            //   <linker>
            //     <parameter value= />
            //     <library name= cache= />
            //   </linker>
            // <options>

#if SINGULARITY
            // read the file
            optManifest = NibHelper.OpenXmlDocument(optionsFile);
            if (optManifest != null) {
                Console.WriteLine("parse of {0} successful", optionsFile);
            }
            else {
                Console.WriteLine("parse of {0} failed", optionsFile);
            }
#else
            optManifest = new XmlDocument();
            optManifest.Load(optionsFile);
#endif
            XmlNode options = GetChild(optManifest, "options");
            if (options == null) {
                WriteLine("nib: error missing top-level <options> tag.");
                return null;
            }
            Options opts = new Options();

            XmlNode[] assemblies = GetChildren(options, "assembly");
            if (assemblies.Length != 0) {
                opts.assemblies = new OReplacement[assemblies.Length];
                for (int a = 0; a < assemblies.Length; a++) {
                    XmlNode anode = assemblies[a];
                    string name = GetAttribute(anode, "name");
                    string version = GetAttribute(anode, "version");

                    if (name == null) {
                        WriteLine("nib: error <assembly> tag missing 'name' attribute.");
                        return null;
                    }

                    OReplacement assembly = new OReplacement(name, version);
                    assembly.PublicKey = GetAttribute(anode, "publickey");
                    assembly.Locale = GetAttribute(anode, "locale");

                    XmlNode[] replacements = GetChildren(anode, "replacement");
                    if (replacements.Length == 0) {
                        WriteLine("nib: error <assembly> tag missing <replacement> child.");
                        return null;
                    }

                    assembly.replacements = new OAssembly[replacements.Length];
                    for (int r = 0; r < replacements.Length; r++) {
                        XmlNode rnode = replacements[r];
                        name = GetAttribute(rnode, "name");
                        version = GetAttribute(rnode, "version");
                        if (name == null) {
                            WriteLine("nib: error <replacement> tag missing 'name' attribute.");
                            return null;
                        }

                        OAssembly replacement = new OAssembly(name, version);
                        replacement.PublicKey = GetAttribute(rnode, "publickey");
                        replacement.Locale = GetAttribute(rnode, "locale");
                        replacement.Cache = GetAttribute(rnode, "cache");
                        assembly.replacements[r] = replacement;
                    }
                    opts.assemblies[a] = assembly;
                }
            }

            // Parse the codegen section.
            XmlNode codegen = GetChild(options, "codegen");
            if (codegen == null) {
                WriteLine("nib: error <options> tag missing <codegen> child.");
            }
            opts.codegen = new OCodegen();

            XmlNode[] parameters = GetChildren(codegen, "parameter");
            if (parameters.Length != 0) {
                string[] pars = new string [parameters.Length];
                for (int p = 0; p < parameters.Length; p++) {
                    string paramValue = GetAttribute(parameters[p], "value");
                    if (paramValue == null) {
                        WriteLine("nib: error <parameter> tag missing 'value' attribute.");
                        return null;
                    }
                    String condition =
                        GetAttribute(parameters[p], "Condition");
                    if (condition != null &&
                        EvalCondition(condition) == false) {
                        pars[p] = "";
                    } else {
                        pars[p] = paramValue;
                    }
                }
                opts.codegen.parameters = pars;
            }

            // Parse the linker section.
            XmlNode linker = GetChild(options, "linker");
            if (linker == null) {
                WriteLine("nib: error <options> tag missing <linker> child.");
            }
            opts.linker = new OLinker();

            parameters = GetChildren(linker, "parameter");
            if (parameters.Length != 0) {
                string[] pars = new string [parameters.Length];
                for (int p = 0; p < parameters.Length; p++) {
                    String paramValue = GetAttribute(parameters[p], "value");
                    if (paramValue == null) {
                        WriteLine("nib: error <parameter> tag missing 'value' attribute.");
                        return null;
                    }
                    String condition =
                        GetAttribute(parameters[p], "Condition");
                    if (condition != null &&
                        EvalCondition(condition) == false) {
                        pars[p] = "";
                    } else {
                        pars[p] = paramValue;
                    }
                }
                opts.linker.parameters = pars;
            }

            XmlNode[] libraries = GetChildren(linker, "library");
            if (libraries.Length != 0) {
                opts.linker.libraries = new OLibrary [libraries.Length];
                for (int l = 0; l < libraries.Length; l++) {
                    XmlNode lnode = libraries[l];
                    string name = GetAttribute(lnode, "name");
                    if (name == null) {
                        WriteLine("nib: error <library> tag missing 'name' attribute.");
                        return null;
                    }

                    OLibrary library = new OLibrary(name);
                    library.Cache = GetAttribute(lnode, "cache");
                    opts.linker.libraries[l] = library;
                }
            }

            return opts;
        }

        private bool EvalCondition(String expression)
        {
            ArrayList tokens = new ArrayList();
            Tokenize(expression, tokens);
            int lastToken;
            bool result = EvalAnd(tokens, 0, out lastToken);
            if (lastToken != tokens.Count) {
                WriteLine("nib: Condition evaluation error");
            }
            return result;
        }

        private bool EvalAnd(ArrayList tokens,
                             int startIndex,
                             out int endIndex)
        {
            if (startIndex == tokens.Count) {
                WriteLine("nib: evaluation error: incomplete expr");
                endIndex = startIndex;
                return false;
            }
            bool leftResult = EvalOr(tokens, startIndex, out endIndex);
            if (endIndex < tokens.Count) {
                if ("and".Equals(tokens[endIndex])) {
                    bool rightResult =
                        EvalAnd(tokens, endIndex + 1, out endIndex);
                    return leftResult && rightResult;
                } else {
                    WriteLine("nib: unexpected token: {0}", tokens[endIndex]);
                    return false;
                }
            } else {
                return leftResult;
            }
        }

        private bool EvalOr(ArrayList tokens,
                            int startIndex,
                            out int endIndex)
        {
            if (startIndex == tokens.Count) {
                WriteLine("nib: evaluation error: incomplete expr");
                endIndex = startIndex;
                return false;
            }
            bool leftResult = EvalEquals(tokens, startIndex, out endIndex);
            if (endIndex < tokens.Count) {
                if ("and".Equals(tokens[endIndex])) {
                    bool rightResult =
                        EvalOr(tokens, endIndex + 1, out endIndex);
                    return leftResult || rightResult;
                } else {
                    WriteLine("nib: unexpected token: {0}", tokens[endIndex]);
                    return false;
                }
            } else {
                return leftResult;
            }
        }

        private bool EvalEquals(ArrayList tokens,
                                int startIndex,
                                out int endIndex)
        {
            if (startIndex == tokens.Count) {
                WriteLine("nib: evaluation error: incomplete expr");
                endIndex = startIndex;
                return false;
            }
            String leftResult = EvalString(tokens, startIndex, out endIndex);
            if (endIndex < tokens.Count) {
                if ("==".Equals(tokens[endIndex])) {
                    String rightResult =
                        EvalString(tokens, endIndex + 1, out endIndex);
                    return leftResult.Equals(rightResult);
                } else if ("!=".Equals(tokens[endIndex])) {
                    String rightResult =
                        EvalString(tokens, endIndex + 1, out endIndex);
                    return !leftResult.Equals(rightResult);
                } else {
                    WriteLine("nib: unexpected token: {0}", tokens[endIndex]);
                    return false;
                }
            } else if (leftResult.Equals("true")) {
                return true;
            } else if (leftResult.Equals("false")) {
                return false;
            } else {
                WriteLine("nib: unexpected boolean expression");
                return false;;
            }
        }

        private String EvalString(ArrayList tokens,
                               int startIndex,
                               out int endIndex)
        {
            if (startIndex == tokens.Count) {
                WriteLine("nib: evaluation error: incomplete expr");
                endIndex = startIndex;
                return "''";
            }
            String token = (String) tokens[startIndex];
            if (token[0] == '\'') {
                endIndex = startIndex + 1;
                return token;
            } else {
                WriteLine("nib: Expected constant: {0}", tokens[startIndex]);
                endIndex = startIndex;
                return "";
            }
        }

        private void Tokenize(String expression, ArrayList tokens)
        {
            int index = 0;
            while (index < expression.Length) {
                if (expression[index] == ' ') {
                    index++;
                } else if (expression[index] == '\'') {
                    int endIndex = expression.IndexOf('\'', index+1);
                    if (endIndex < 0) {
                        endIndex = expression.Length - 1;
                    }
                    String quotedString =
                        expression.Substring(index, endIndex - index + 1);
                    String token = SubstituteEnvVars(quotedString);
                    tokens.Add(token);
                    index = endIndex + 1;
                } else if (LookingAt(expression, index, "==") ||
                           LookingAt(expression, index, "!=")) {
                    tokens.Add(expression.Substring(index, 2));
                    index += 2;
                } else if (LookingAtWord(expression, index, "and")) {
                    tokens.Add(expression.Substring(index, 3));
                    index += 3;
                } else if (LookingAtWord(expression, index, "or")) {
                    tokens.Add(expression.Substring(index, 2));
                    index += 2;
                } else if (expression[index] >= '0' &&
                           expression[index] <= '9') {
                    int endIndex = index + 1;
                    while (endIndex < expression.Length &&
                           expression[endIndex] >= '0' &&
                           expression[endIndex] <= '9') {
                        endIndex++;
                    }
                    tokens.Add(expression.Substring(index, endIndex - index));
                    index = endIndex;
                } else if ((expression[index] >= 'a' &&
                            expression[index] <= 'z') ||
                           (expression[index] >= 'A' &&
                            expression[index] <= 'Z')) {
                    int endIndex = index + 1;
                    while (endIndex < expression.Length &&
                           ((expression[endIndex] >= 'a' &&
                             expression[endIndex] <= 'a') ||
                            (expression[endIndex] >= 'A' &&
                             expression[endIndex] <= 'Z') ||
                            (expression[endIndex] >= '0' &&
                             expression[endIndex] <= '9'))) {
                        endIndex++;
                    }
                    tokens.Add(expression.Substring(index, endIndex - index));
                    index = endIndex;
                } else {
                    WriteLine("nib: Cannot tokenize conditional expression;\n\t{0}", expression);
                }
            }
        }

        private bool LookingAt(String bigString,
                               int startIndex,
                               String matchString)
        {
            int matchLength = matchString.Length;
            for (int i = 0; i < matchLength; i++) {
                if (startIndex + i >= bigString.Length ||
                    bigString[startIndex + i] != matchString[i]) {
                    return false;
                }
            }
            return true;
        }

        private bool LookingAtWord(String bigString,
                                   int startIndex,
                                   String matchString)
        {
            int matchLength = matchString.Length;
            for (int i = 0; i < matchLength; i++) {
                if (startIndex + i >= bigString.Length ||
                    bigString[startIndex + i] != matchString[i]) {
                    return false;
                }
            }
            if (bigString.Length == startIndex + matchLength) {
                return true;
            }
            char nextChar = bigString[startIndex + matchLength];
            return !((nextChar >= 'a' && nextChar <= 'z') ||
                     (nextChar >= 'A' && nextChar <= 'Z'));
        }

        private String SubstituteEnvVars(String expression)
        {
#if SINGULARITY
            return expression;
#else
            int dollarIndex = expression.IndexOf('$');
            if (dollarIndex >= 0) {
                if (expression.Length == dollarIndex + 1 ||
                    expression[dollarIndex+1] != '(') {
                    WriteLine("nib: condition with $ not followed by (");
                    return expression;
                }
                int endIndex = expression.IndexOf(')', dollarIndex+1);
                int length = endIndex - dollarIndex - 2;
                String variableName =
                    expression.Substring(dollarIndex + 2, length);
                String prefix =
                    expression.Substring(0, dollarIndex);
                String varString =
                    Environment.GetEnvironmentVariable(variableName);
                String suffix =
                    SubstituteEnvVars(expression.Substring(endIndex + 1));
                return prefix + varString + suffix;
            } else {
                return expression;
            }
#endif
        }

        class Application
        {
            public bool executable;
            public readonly string Name;
            public readonly string Runtime;
            public OAssembly[] assemblies = new OAssembly[0];
            // products:
            public string obj;
            public string info;
            public string super;
            public string mname;    // Machine executable name.
            public string mpath;    // Machine executable local path.
            public string mpdb;     // Machine executable pdb.
            public string mmani;    // Output manifest
            public string map;
            public string log;

            public OCodegen codegen;
            public OLinker linker;

            public Application(string name, string runtime)
            {
                this.Name = name;
                this.Runtime = runtime;
            }
        }

        ///////////////////////////////////////////////////// Manifest Processing.
        //
        // <application name=>
        //   <process id= main= path= cache=>
        //     <assemblies>
        //       <assembly name= version= publickey= cache= />
        //     </assemblies>
        //     <categories>
        //       <category id= name= class=>
        //         <device signature=  />
        //         <ioPortRange id= baseAddress= rangeLength= />
        //         <ioIrqRange id= baseAddress= rangeLength= />
        //         <ioDmaRange id= baseAddress= rangeLength= />
        //         <ioMemoryRange addressBits= alignment= rangeLength= fixed= />
        //         <extension id= startStateId= contractName= endpointEnd= assembly=...>
        //           <imp>
        //             <inherit name= />
        //           </imp>
        //           <exp>
        //             <inherit name= />
        //           </exp>
        //         </extension>
        //         <serviceProvider id= startStateId= contractName= endpointEnd= assembly=...>
        //           <imp>
        //             <inherit name= />
        //           </imp>
        //           <exp>
        //             <inherit name= />
        //           </exp>
        //         </serviceProvider>
        //       </category>
        //     </categories>
        //     <codegen>
        //       <parameter value= />
        //     </codegen>
        //     <linker>
        //       <parameter value= />
        //     </linker>
        //   </process>
        // </application>
        //
#if !SINGULARITY
        private Application ParseApplicationManifest(string applicationFile,
                                                     out XmlDocument appManifest)
        {
            appManifest = new XmlDocument();
            appManifest.Load(applicationFile);
#else
        private Application ParseApplicationManifest(string applicationFile,
                                                     out XmlNode appManifest)
        {
            appManifest = NibHelper.CreateXmlDocument(applicationFile);
#endif

            XmlNode application  = GetChild(appManifest, "application");
            if (application == null) {
                WriteLine("nib: error missing top-level <application> tag.");
                return null;
            }
            string name = GetAttribute(application, "name");
            if (name == null) {
                WriteLine("nib: error <application> tag missing 'name' attribute.");
                return null;
            }
            string runtime = GetAttribute(application, "runtime");
            if (runtime == null) {
                WriteLine("nib: error <application> tag missing 'runtime' attribute.");
                return null;
            }
            XmlNode process = GetChild(application, "process");
            if (process == null) {
                WriteLine("nib: error <application> tag missing <process> child tag.");
                return null;
            }
#if !SINGULARITY
            Application app = new Application(name, runtime);
#else
            string strongName = GetAttribute(process, "strongname");
            if (strongName == null) {
                WriteLine("nib: error <application> tag missing 'strongname' attribute.");
                return null;
            }
            Application app = new Application(strongName, runtime);
#endif

            app.executable = GetAttribute(process, "executable") == "true";
            XmlNode assemblyHolder = GetChild(process, "assemblies");
            if (assemblyHolder != null) {
                XmlNode[] assemblies = GetChildren(assemblyHolder, "assembly");
                if (assemblies.Length != 0) {
                    app.assemblies = new OAssembly[assemblies.Length];
                    for (int a = 0; a < assemblies.Length; a++) {
                        XmlNode anode = assemblies[a];
                        name = GetAttribute(anode, "name");
                        string version = GetAttribute(anode, "version");
                        if (name == null) {
                            WriteLine("nib: error <assembly> tag missing 'name' attribute.");
                            return null;
                        }

                        OAssembly assembly = new OAssembly(name, version);
                        assembly.Cache = GetAttribute(anode, "cache");
                        assembly.Locale = GetAttribute(anode, "locale");
                        assembly.PublicKey = GetAttribute(anode, "publickey");

                        app.assemblies[a] = assembly;
                    }
                }
            }

            // Parse the optional codegen section.
            XmlNode codegen = GetChild(process, "codegen");
            if (codegen != null) {
                app.codegen = new OCodegen();

                XmlNode[] parameters = GetChildren(codegen, "parameter");
                if (parameters.Length != 0) {
                    string[] pars = new string [parameters.Length];
                    for (int p = 0; p < parameters.Length; p++) {
                        pars[p] = GetAttribute(parameters[p], "value");
                        if (pars[p] == null) {
                            WriteLine("nib: error <parameter> tag missing 'value' attribute.");
                            return null;
                        }
                    }
                    app.codegen.parameters = pars;
                }
            }

            // Parse the optional linker section.
            XmlNode linker = GetChild(process, "linker");
            if (linker != null) {
                app.linker = new OLinker();

                XmlNode[] parameters = GetChildren(linker, "parameter");
                if (parameters.Length != 0) {
                    string[] pars = new string [parameters.Length];
                    for (int p = 0; p < parameters.Length; p++) {
                        pars[p] = GetAttribute(parameters[p], "value");
                        if (pars[p] == null) {
                            WriteLine("nib: error <parameter> tag missing 'value' attribute.");
                            return null;
                        }
                    }
                    app.linker.parameters = pars;
                }

    #if false
                // For now, we don't allow libraries in the application linker section.
                XmlNode[] libraries = GetChildren(linker, "library");
                if (libraries.Length != 0) {
                    app.linker.libraries = new OLibrary [libraries.Length];
                    for (int l = 0; l < libraries.Length; l++) {
                        XmlNode lnode = libraries[l];
                        string lname = GetAttribute(lnode, "name");
                        if (lname == null) {
                            WriteLine("nib: error <library> tag missing 'name' attribute.");
                            return null;
                        }

                        OLibrary library = new OLibrary(lname);
                        library.Cache = GetAttribute(lnode, "cache");
                        app.linker.libraries[l] = library;
                    }
                }
    #endif
            }

            return app;
        }

        private bool SetMaxChildId(XmlNode root, string xmlGroup)
        {
            XmlNode group = GetChild(root, xmlGroup);
            if (group != null) {
                int maxId = -1;
                foreach (XmlNode node in group.ChildNodes) {
                    int id = GetAttribute(node, "id", -1);
                    if (id == -1) {
                        id = maxId + 1;
                        InsertAttribute(node, "id", id.ToString());
                    }
                    maxId = Math.Max(maxId, id);
                }
                InsertAttribute(group, "length", (maxId + 1).ToString());
                return true;
            }
            else {
                group = AddElement(root, xmlGroup);
                AddAttribute(group, "length", "0");
            }
            return false;
        }

        private bool InstallApplicationManifest(Application app)
        {
            XmlNode application = GetChild(appManifest, "application");
            if (application == null) {
                WriteLine("nib: error missing top-level <application> tag.");
                return false;
            }

            int maxProcessId = -1;
            foreach (XmlNode process in GetChildren(application, "process")) {
                int maxCategoryId = -1;

                maxProcessId = Math.Max(maxProcessId, GetAttribute(process, "id", -1));
                // look for categories node

                foreach (XmlNode categories in GetChildren(process, "categories")) {
                    foreach (XmlNode category in GetChildren(categories, "category")) {
                        maxCategoryId++;
                        SetMaxChildId(category, "endpoints");
                        string name = GetAttribute(category, "name", "");
                        if (name == "driver") {
                            SetMaxChildId(category, "fixedHardware");
                            SetMaxChildId(category, "dynamicHardware");
                            SetMaxChildId(category, "configs");
                        }
                        // set these for all categories
                        SetMaxChildId(category, "StringParameters");
                        SetMaxChildId(category, "StringArrayParameters");
                        SetMaxChildId(category, "LongParameters");
                        SetMaxChildId(category, "BoolParameters");
                    }
                }
                AddAttribute(process, "categories", (maxCategoryId + 1).ToString());
                if (app.executable) {
                    AddAttribute(process, "path", app.mname);
                }
            }
            AddAttribute(application, "processes", (maxProcessId + 1).ToString());
            return true;
        }
        //
        //////////////////////////////////////////////////////////////////////////

        private OAssembly[] FindReplacements(OAssembly assembly)
        {
            if (options == null) {
                return null;
            }

            OAssembly[] assemblies = options.assemblies;

            for (int a = 0; a < assemblies.Length; a++) {
                if (assembly.IsMatch(assemblies[a])) {
                    return ((OReplacement)assemblies[a]).replacements;
                }
            }
            return null;
        }

        private OAssembly[] FindReplacements(OAssembly[] assemblies)
        {
            // Short circuit search if there are no replacement assemblies.
            if (options == null || options.assemblies == null) {
                return assemblies;
            }

            ArrayList list = new ArrayList();

            for (int a = 0; a < assemblies.Length; a++) {
                OAssembly[] replacements = FindReplacements(assemblies[a]);
                if (replacements != null) {
                    for (int r = 0; r < replacements.Length; r++) {
                        if (Verbose) {
                            WriteLine("Replacing {0} with {1}", assemblies[a], replacements[r]);
                        }
                        list.Add(replacements[r]);
                    }
                }
                else {
                    list.Add(assemblies[a]);
                }
            }

            OAssembly[] final = new OAssembly[list.Count];
            for (int a = 0; a < list.Count; a++) {
                final[a] = (OAssembly)list[a];
            }
            return final;
        }

        public void DeleteIfExists(string file)
        {
            if (File.Exists(file)) {
                File.Delete(file);
                if (Verbose) {
                    WriteLine("deleted {0}", file);
                }
            }
        }

        private static string ReplaceSuffix(string value, string old, string suffix)
        {
            if (value.EndsWith(old)) {
                return value.Substring(0, value.Length - old.Length) + suffix;
            }
            return value + suffix;
        }
        
        private static void AppendArg(StringBuilder sb, string arg)
        {
            bool containsSpaces = false;
            for (int i=0; i < arg.Length; i++)
            {
                if (arg[i] == ' ')
                {
                    containsSpaces = true;
                    break;
                }
            }
            if (containsSpaces)
            {
                sb.Append(" \"");
                sb.Append(arg.Replace("\"", "\"\""));
                sb.Append("\"");
            }
            else
            {
                sb.Append(" ");
                sb.Append(arg.Replace("\"", "\"\""));
            }
        }

        // Returns true of the application was built or is up to date.
#if !SINGULARITY
        private bool BuildApplication(string applicationFile)
        {
#else
        private bool BuildApplication(string applicationFile, PipeMultiplexer! outputMux)
        {
#endif
            StreamWriter log = null;
            DateTime logBegin = DateTime.Now;
            bool doCodegen = DoCodegen;
            bool doLinker = DoLinker;
            bool doManifest = DoManifest;
            bool doForceCodegen = ForceCodegen;
            bool doForceLinker = ForceLinker;
            bool doForceManifest = ForceManifest;

            try {
                Application app = ParseApplicationManifest(applicationFile, out appManifest);
                if (app == null) {
                    // parse failed.
                    return false;
                }
                if (!app.executable) {
                    if (String.Compare(app.Name, "kernel", true) != 0) {
                        WriteLine(
                            "nib: Warning: Not executable process in manifest: {0}.",
                            applicationFile);
                    }
                    doCodegen = false;
                    doLinker = false;
                    doForceCodegen = false;
                    doForceLinker = false;
                }

                // WriteLine("    {0}", applicationFile);

                app.obj = tempDirectory + app.Name + ".obj";
                app.info = tempDirectory + app.Name + "_info.obj";
                app.super = tempDirectory + app.Name + "_superObj.obj";
                app.map = tempDirectory + app.Name + ".map";
                app.log = tempDirectory + app.Name + ".log";
                app.mname = app.Name + "." + architecture;
                app.mpath = nativeDirectory + app.mname;
 #if !SINGULARITY
                app.mmani = nativeDirectory + app.Name + ".manifest";
#else
                app.mmani = nativeDirectory + app.Name + ".resolved.manifest";
#endif
                app.mpdb = nativeDirectory + app.Name + "." + architecture + ".pdb";

                DateTime written;
                DateTime update =
                    optionsFile == null ? DateTime.MinValue : File.GetLastWriteTime(optionsFile);
                DateTime newest = File.GetLastWriteTime(applicationFile);
                if (update < newest) {
                    update = newest;
                }

                if (Verbose) {
                    WriteLine("Name = [{0}]", app.Name);
                    WriteLine("obj  = [{0}]", app.obj);
                    WriteLine("info = [{0}]", app.info);
                    WriteLine("super= [{0}]", app.super);
                    WriteLine("mpdb = [{0}]", app.mpdb);
                    WriteLine("mname= [{0}]", app.mname);
                    WriteLine("mpath= [{0}]", app.mpath);
                    WriteLine("mmani= [{0}]", app.mmani);
                    WriteLine("map  = [{0}]", app.map);
                    WriteLine("log  = [{0}]", app.log);
                }

                if (DoClean) {
                    DeleteIfExists(app.obj);
                    DeleteIfExists(app.info);
                    DeleteIfExists(app.super);
                    DeleteIfExists(app.mpdb);
                    DeleteIfExists(app.mpath);
                    DeleteIfExists(app.mmani);
                    DeleteIfExists(app.map);
                    DeleteIfExists(app.log);
                    return true;
                }

#if VERY_VERBOSE
                WriteLine("  >>> Pre replace ***");
                for (int a = 0; a < app.assemblies.Length; a++) {
                    if (Verbose) {
                        WriteLine("  {0}", app.assemblies[a]);
                        WriteLine("     {0}", app.assemblies[a].Cache);
                        WriteLine("     {0}", app.assemblies[a].Final);
                    }
                }
#endif
                // Now that we've parsed the manifest, we replace any mapped assemblies.
                OAssembly[] assemblies = FindReplacements(app.assemblies);

#if VERY_VERBOSE
                WriteLine("  <<< Post replace ***");
#endif
                // Now, find the final code (if it isn't already cached).
                for (int a = 0; a < assemblies.Length; a++) {
                    string test = cacheDirectory + assemblies[a].Name;
                    if (File.Exists(test)) {
                        assemblies[a].Final = test;
                    }
                    else {
                        if (assemblies[a].Cache == null) {
                            WriteLine("nib: error Couldn't find assembly: {0}", assemblies[a]);
                            return false;
                        }
                        string assemblyCachePath = assemblies[a].Cache.Replace('/', '\\');
                        string finalAssemblyPath = null;
                        if (cacheDirectory == null) {
                            finalAssemblyPath = assemblyCachePath;
                        }
                        else {
                            // The paths in the manifest can contain leading slashes.
                            // Don't let that confuse Path.Combine -- strip them first.
                            int skip = 0;
                            while (skip < assemblyCachePath.Length && (
                                assemblyCachePath[skip] == '/' || assemblyCachePath[skip] == '\\')) {
                                skip++;
                            }
                            if (skip > 0) {
                                assemblyCachePath = assemblyCachePath.Substring(skip);
                            }
                            finalAssemblyPath = Path.Combine(cacheDirectory, assemblyCachePath);
                        }
                        assemblies[a].Final = finalAssemblyPath;
                    }

#if VERY_VERBOSE
                    if (Verbose) {
                        WriteLine("  {0}", assemblies[a]);
                        WriteLine("     {0}", assemblies[a].Cache);
                        WriteLine("     {0}", assemblies[a].Final);
                    }
#endif
                }

                if (doCodegen) {
                    // Verify that all of the assemblies exist.
                    bool errorFound = false;
                    for (int a = 0; a < assemblies.Length; a++) {
                        if (!File.Exists(assemblies[a].Final)) {
                            WriteLine("nib: error Couldn't find assembly file: {0}",
                                              assemblies[a].Final);
                            errorFound = true;
                        }
                    }
                    if (errorFound) {
                        return false;
                    }

                    // Find the newest assembly.
                    newest = update;
                    for (int a = 0; a < assemblies.Length; a++) {
                        written = File.GetLastWriteTime(assemblies[a].Final);
                        if (newest < written) {
                            newest = written;
                        }
                    }

                    DateTime target = DateTime.MinValue;
                    if (File.Exists(app.obj)) {
                        target = File.GetLastWriteTime(app.obj);
                    }

                    if (newest >= target || doForceCodegen) {
                        // Codegen is necessary.

                        // <option>
                        // /out: ..\obj\Prototype.MarkSweep\Shell.obj
                        // /outdir: ..\obj\Prototype.MarkSweep
                        // <assembly>

                        log = new StreamWriter(app.log);

                        StringBuilder sb = new StringBuilder(4096);
                        ArrayList bartokOptions = new ArrayList();
                        if (architecture == "x64") {
                            sb.Append(" /x64 ");
                        }
                        if (options != null) {
                            foreach (string arg in options.codegen.parameters) {
                                bartokOptions.Add(arg);
                            }
                        }
                        if (app.codegen != null) {
                            foreach (string arg in app.codegen.parameters) {
                                bartokOptions.Add(arg);
                            }
                        }
                        bartokOptions.Add("/out:" + app.obj);
                        bartokOptions.Add("/outdir:" + tempDirectory.Trim('\\'));

                        foreach (string bartokOption in bartokOptions) {
                            AppendArg(sb, bartokOption);
                        }

                        for (int a = 0; a < assemblies.Length; a++) {
                            AppendArg(sb, assemblies[a].Final);
                        }

                        log.WriteLine("# Name = [{0}]", app.Name);
                        log.WriteLine("# obj  = [{0}]", app.obj);
                        log.WriteLine("# info = [{0}]", app.info);
                        log.WriteLine("# super= [{0}]", app.super);
                        log.WriteLine("# mpdb = [{0}]", app.mpdb);
                        log.WriteLine("# mname= [{0}]", app.mname);
                        log.WriteLine("# mpath= [{0}]", app.mpath);
                        log.WriteLine("# map  = [{0}]", app.map);
                        log.WriteLine("# log  = [{0}]", app.log);
                        log.WriteLine("# target = {0}", target);
                        log.WriteLine("# newest = {0}", newest);
                        log.WriteLine("# bartok = {0}", Bartok);

                        //log.WriteLine("#{0} {1}", Bartok, sb.ToString());

                        log.WriteLine("# Bartok Options:");
                        foreach (string option in bartokOptions) {
                            log.WriteLine("#    " + option);
                        }

                        log.WriteLine("# Assemblies:");
                        foreach (OAssembly asm in assemblies) {
                            log.WriteLine("#    " + asm.Name);
                        }

                        if (Verbose) {
                            WriteLine(sb.ToString());
                        }

                        Console.WriteLine("NIB: Generating Native Image - {0}", Path.GetFileNameWithoutExtension(app.Name));
#if !SINGULARITY
                        string bartokCommandLine = sb.ToString();

                        string emitBartokCommandFileText = Environment.GetEnvironmentVariable("EmitBartokCommandFile");
                        bool emitBartokCommandFile = String.Equals(emitBartokCommandFileText, "true", StringComparison.OrdinalIgnoreCase);
                        if (emitBartokCommandFile) {
                            string bartokCommandFilePath = Path.Combine(tempDirectory, app.Name + ".bartok.cmd");
                            try {
                                string bartokCommandFileContents = Bartok + " " + bartokCommandLine + "\r\n";
                                File.WriteAllText(bartokCommandFilePath,  bartokCommandFileContents, Encoding.ASCII);
                                Console.Error.WriteLine("info : Saved Bartok invocation to file: {0}", bartokCommandFilePath);
                            }
                            catch (Exception ex) {
                                Console.Error.WriteLine("warning : Failed to write file '{0}'.", bartokCommandFilePath);
                                Console.Error.WriteLine("warning : {0}: {1}", ex.GetType().Name, ex.Message);
                            }
                        }


                        if (!RunProgram("bartok", app.Name, Bartok, bartokCommandLine, log)) {
                            Console.WriteLine("{0}(0): log file", app.log);
                            return false;
                        }
#else
                        if (!RunProgram("bartok", app.Name, Bartok, sb.ToString(), log, outputMux)) {
                            return false;
                        }
#endif
                    }
                    else {
                        if (Verbose) {
                            WriteLine("nib: Codegen unneeded: {0}", app.obj);
                        }
                    }
                }

                // Now that we've parsed the manifest, we replace any mapped libraries.
                OLibrary[] libraries =
                    options != null ? options.linker.libraries : new OLibrary[0];

                // Now, find the final code (if it isn't already cached).
                for (int l = 0; l < libraries.Length; l++) {
                    string name = libraries[l].Name;
                    string cache = libraries[l].Cache;

                    name = name.Replace("$(RUNTIME)", app.Runtime);
                    if (cache != null) {
                        cache = cache.Replace("$(RUNTIME)", app.Runtime);
                    }

                    string test = libCacheDirectory + name;
                    //WriteLine(" looking for final code @ {0}", test);
                    if (File.Exists(test)) {
                        libraries[l].Final = test;
                        if (cache == null) {
                            libraries[l].Cache = libraries[l].Name;
                            cache = name;
                        }
                    }
                    else {
                        if (cache == null) {
                            WriteLine("nib: error Couldn't find library: {0}",
                                              libraries[l]);
                            return false;
                        }

                        libraries[l].Final = Path.Combine(cacheDirectory,
                            cache.Replace('/', '\\'));
                    }

                    if (Verbose) {
                        WriteLine("  {0}", cache);
                        WriteLine("     {0}", libraries[l].Final);
                    }
                }

                if (doLinker) {
                    // Verify that all of the libraries exist.
                    for (int l = 0; l < libraries.Length; l++) {
                        if (!File.Exists(libraries[l].Final)) {
                            WriteLine("nib: error Couldn't find library: {0}", libraries[l].Final);
                            return false;
                        }
                    }

                    if (!File.Exists(app.obj)) {
                        WriteLine("nib: error Couldn't find file: {0}", app.obj);
                        return false;
                    }
                    if (!File.Exists(app.super)) {
                        WriteLine("nib: error Couldn't find file: {0}", app.super);
                        return false;
                    }

                    // Find the newest library.
                    newest = update;
                    for (int l = 0; l < libraries.Length; l++) {
                        written = File.GetLastWriteTime(libraries[l].Final);
                        if (newest < written) {
                            newest = written;
                        }
                    }
                    written = File.GetLastWriteTime(app.obj);
                    if (newest < written) {
                        newest = written;
                    }
                    written = File.GetLastWriteTime(app.super);
                    if (newest < written) {
                        newest = written;
                    }

                    DateTime target = DateTime.MinValue;
                    if (File.Exists(app.mpath)) {
                        target = File.GetLastWriteTime(app.mpath);
                    }


                    if (newest >= target || doForceLinker) {
                        // Linking is necessary

                        // <option>
                        // /out:..\obj\Prototype.MarkSweep\Shell.$(machine)
                        // /pdb:..\obj\Prototype.MarkSweep\Shell.$(machine).pdb
                        // ..\obj\Prototype.MarkSweep\Shell.obj
                        // ..\obj\Prototype.MarkSweep\Shell_superObj.obj
                        // <library>
                        StringBuilder sb = new StringBuilder(4096);
                        if (options != null) {
                            foreach (string arg in options.linker.parameters) {
                                AppendArg(sb, arg);
                            }
                        }
                        if (app.linker != null) {
                            foreach (string arg in app.linker.parameters) {
                                AppendArg(sb, arg);
                            }
                        }
                        sb.Append(" /machine:");
                        if (architecture =="arm") {
                            sb.Append("arm");
                        }
                        else {
                            sb.Append(architecture);
                        }
                        AppendArg(sb, "/out:" + app.mpath);
                        AppendArg(sb, "/pdb:" + app.mpdb);
                        AppendArg(sb, "/map:" + app.map);
                        AppendArg(sb, app.obj);
                        AppendArg(sb, app.super);
                        for (int l = 0; l < libraries.Length; l++) {
                            AppendArg(sb, libraries[l].Final);
                        }
                        String args = sb.ToString();

#if !SINGULARITY
                        if (log != null) {
                            log.WriteLine("#{0} {1}", LinkExecutablePath, args);
                        }
                        Console.WriteLine("NIB: Linking                 - {0}", Path.GetFileNameWithoutExtension(app.Name));
                        if (!RunProgram("link", app.Name, LinkExecutablePath, args, null)) {
                            return false;
                        }
#else
                        if (log != null) {
                            log.WriteLine("#{0} {1}", "link", args);
                        }
                        if (!RunProgram("link", app.Name, "link", args, null, outputMux)) {
                            return false;
                        }
#endif

#if CALL_MKNAME
                        args = "/u " + app.mpath;
                        if (log != null) {
                            log.WriteLine("#{0} {1}", "mkname.exe", args);
                        }
#if !SINGULARITY
                        if (!RunProgram("mkname", app.Name, "mkname.exe", args, null)) {
                            return false;
                        }
#else
                        if (!RunProgram("mkname", app.Name, "mkname.exe", args, null, PipeMultiplexer! outputMux)) {
                            return false;
                        }
#endif
#endif
                    }
                    else {
                        if (Verbose) {
                            WriteLine("nib: Linker unneeded: {0}", app.mpath);
                        }
                    }
                }

                if (doManifest) {
                    // Find the newest library.
                    newest = File.GetLastWriteTime(applicationFile);
                    if (newest < update) {
                        newest = update;
                    }
                    DateTime target = DateTime.MinValue;
                    if (File.Exists(app.mmani)) {
                        target = File.GetLastWriteTime(app.mmani);
                    }

                    if (newest >= target || doForceManifest) {
                        //////////////////////////////////////////////////////////
                        // Update the installed manifest and write it out.
                        //
                        if (!InstallApplicationManifest(app)) {
                            return false;
                        }
#if SINGULARITY
                        Console.WriteLine(" manifest ->{0}", app.mmani);
                        FileStream fs = new FileStream(app.mmani, FileMode.Create,
                            FileAccess.Write, FileShare.None);
                        XmlWriter writer = new XmlWriter(fs, System.Text.Encoding.UTF8);
                        //writer.Formatting = Formatting.Indented;
                        NibHelper.SaveXmlDocument(writer, appManifest);
                        writer.Close();
#else
                        XmlTextWriter writer = new XmlTextWriter(app.mmani,
                                                                 System.Text.Encoding.UTF8);
                        writer.Formatting = Formatting.Indented;
                        appManifest.Save(writer);
                        writer.Close();
#endif
                    }
                    else {
                        if (Verbose) {
                            WriteLine("nib: Manifest generation unneeded: {0}", app.mmani);
                        }
                    }
                }
            }
            finally {
                if (log != null) {
                    TimeSpan elapsed = DateTime.Now - logBegin;
                    log.WriteLine("# {0} seconds elapsed.", elapsed.TotalSeconds);
                    log.Close();
                }
                appManifest = null;
            }
            return true;
        }

        #region Parallel Build Support

        // Per-worker state
        string _PrintPrefix = "";
        IntPtr _ChildProcessorAffinityMask;

        // Global state
        readonly static Object _PrintLock = new Object();
        static IntPtr _JobHandle;

        void WriteLine(string line)
        {
            if (_BuildingParallel) {
                Monitor.Enter(_PrintLock);
            }

            if (_PrintPrefix != null && _PrintPrefix.Length != 0) {
                Console.WriteLine(_PrintPrefix + line);
            }
            else {
                Console.WriteLine(line);
            }
            if (_BuildingParallel) {
                Monitor.Exit(_PrintLock);
            }
        }

        void WriteLine(string format, params object[] args)
        {
            WriteLine(String.Format(format, args));
        }


        class QueueJob
        {
            public string Filename;
            public bool Result;
        }

        // Contains
        static readonly Object _JobQueueMonitor = new Object();
        static readonly Queue _WaitingJobQueue = new Queue();
        static readonly ArrayList _FinishedJobList = new ArrayList();
        static bool _BuildingParallel;

        class WorkerThread
        {
            public string DebugPrefix = "";

            // zero-based processor index
            public int Id;
            public Thread Thread;

            public TimeSpan KernelTime;
            public TimeSpan UserTime;


            public WorkerThread(NativeImageBuilder nib, int id)
            {
                this.Id = id;
                this.nib = nib;
                this.DebugPrefix = String.Format("{0}> ", id);
                this._printPrefix = String.Format("{0}> ", id);
            }

            string _printPrefix;

            NativeImageBuilder nib;

            void DebugLine(string line)
            {
#if !SINGULARITY
                Debug.WriteLine(DebugPrefix + line);
#else
                DebugStub.WriteLine(DebugPrefix + line);
#endif
            }

            void DebugLine(string format, params object[] args)
            {
                DebugLine(String.Format(format, args));
            }

            void WriteLine(string line)
            {
                Console.WriteLine(DebugPrefix + line);
            }

            void WriteLine(string format, params object[] args)
            {
                WriteLine(String.Format(format, args));
            }

            public void ThreadMain()
            {
                try {
                    RunQueuedJobs();
                }
                catch (Exception e) {
                    ShowException(e);
                    Environment.Exit(1);
                }
            }

            private void RunQueuedJobs()
            {
                DebugLine("worker thread started");
#if SINGULARITY
                // build a pipe mux
                // Use a mux to splice our own output together with the child
                // processes we will run.
                // Make ourselves a new output pipe
                UnicodePipeContract.Exp! newOutputExp;
                UnicodePipeContract.Imp! newOutputImp;
                UnicodePipeContract.NewChannel(out newOutputImp, out newOutputExp);

                // Retrieve our real stdOut and start using the one we just created
                // instead
                UnicodePipeContract.Imp stdOut = ConsoleOutput.Swap(newOutputImp);
                PipeMultiplexer! outputMux = PipeMultiplexer.Start(stdOut, newOutputExp);
#endif
                for (;;) {
                    QueueJob job;

                    lock (_JobQueueMonitor) {
                        if (_WaitingJobQueue.Count == 0) {
                            DebugLine("no more jobs in queue -- exiting");
                            break;
                        }

                        job = (QueueJob)_WaitingJobQueue.Dequeue();
                    }

                    // Execute the job.

                    DebugLine("starting job - " + job.Filename);
                    job.Result = false;
                    try {
#if !SINGULARITY
                        job.Result = nib.BuildApplication(job.Filename);
#else
                        job.Result = nib.BuildApplication(job.Filename, outputMux);
#endif
                    }
                    catch (Exception e) {
                        Console.WriteLine("Failed building {0}\n", job.Filename);
                        throw e;
                    }

                    DebugLine("job finished - " + job.Filename);

                    if (!job.Result) {
                        DebugLine("job FAILED - " + job.Filename);
                    }

                    lock (_JobQueueMonitor) {
                        _FinishedJobList.Add(job);
                    }
                }
#if !SINGULARITY
                long creationTime;
                long exitTime;
                long kernelTime;
                long userTime;
                if (Kernel32.GetThreadTimes(Kernel32.GetCurrentThread(), out creationTime, out exitTime, out kernelTime, out userTime)) {
                    this.KernelTime = new TimeSpan(kernelTime);
                    this.UserTime = new TimeSpan(userTime);
                }
                else {
                    WriteLine("Failed to get thread times!  error = " + Marshal.GetLastWin32Error());
                }
#else
                WriteLine("need to fix up thread times");
                outputMux.Dispose();
#endif
            }
        }

        static void ShowException(Exception chain)
        {
            for (Exception ex = chain; ex != null; ex = ex.InnerException) {
                Console.WriteLine("{0}: {1}", ex.GetType().FullName, ex.Message);
            }
        }

        #endregion


        // Print the correct command line args for the program
        static void Usage()
        {
            Console.WriteLine(
                "Usage:\n" +
                "    nib /cache:<path> [options] app.manifest [app1.manifest ...]\n" +
                "Options:\n" +
                "    /apps:<file>        - File containing list of app.manifests.\n" +
                "    /bartok:<file>      - Specify Bartok to use.\n" +
                "    /c                  - Code generation only, don't link.\n" +
                "    /clean              - Clean up output only.\n" +
                "    /cache:<path>       - Root of file cache.\n" +
                "    /force              - Force generation even if output files are newer.\n" +
                "    /link               - Link only, don't compile.\n" +
                "    /libcache:<path>    - Root of Architecture-dependent cache.\n" +
                "    /machine:<arch>     - Binary target architecture.\n" +
                "    /manifest           - Generate reified manifest only.\n" +
                "    /native:<path>      - Root of native files.\n" +
                "    /options:<file>     - Options manifest file.\n" +
                "    /temp:<path>        - Temporary directory.\n" +
                "    /v                  - Verbose logging output.\n" +
                "    /par                - Disable parallel builds.\n" +
                "    /par:<nn>           - Set to use <nn> parallel processors.\n" +
                "Summary:\n" +
                "    Create a native image for each application manifest.\n" +
                "");
        }

        static bool ReadInfilesFromFile(string filePath, ArrayList infiles)
        {
            try {
                using (StreamReader sr = new StreamReader(filePath)) {
                    string line;
                    while ((line = sr.ReadLine()) != null) {
                        infiles.Add(line);
                    }
                }
                return true;
            }
            catch (FileNotFoundException) {
                Console.WriteLine("File not found {0}\n", filePath);
                return false;
            }
        }

        static bool IsQuote(char c)
        {
            return (c == '"' || c == '\'');
        }

        static string RemoveQuotes(string text)
        {
            int start = 0;
            int length = text.Length;

            if (length > 0 && IsQuote(text[start + length - 1])) {
                length--;
            }
            if (length > 0 && IsQuote(text[start])) {
                start++;
                length--;
            }

            if (start != 0 || length != text.Length) {
                return text.Substring(start, length);
            }
            else {
                return text;
            }
        }

        static bool StringIsNullOrEmpty(string str)
        {
            return (str == null || str.Length == 0);
        }

        static int Main(string[] args)
        {
            ArrayList infiles = new ArrayList();
            string optionsFile = null;
            string cacheDirectory = null;
            string libCacheDirectory = null;
            string tempDirectory = null;
            string nativeDirectory = null;

            Began = DateTime.Now;

            _BuildingParallel = false;

#if !SINGULARITY
            int maxWorkerThreads = Kernel32.GetProcessorCount();
#else
            int maxWorkerThreads = 1;
#endif
            int numWorkerThreads = maxWorkerThreads;

            // Temporaries for command-line parsing
            bool needHelp = (args.Length == 0);

#if !SINGULARITY
            for (int i = 0; i < args.Length && !needHelp; i++) {
#else
            for (int i = 1; i < args.Length && !needHelp; i++) {
#endif
                string arg = (string) args[i];
                arg = RemoveQuotes(arg);

                if (arg.Length >= 2 && (arg[0] == '-' || arg[0] == '/')) {
                    string name = null;
                    string value = null;
                    int n = arg.IndexOf(':');
                    if (n > -1) {
                        name = arg.Substring(1, n - 1).ToLower();
                        if (n < arg.Length + 1) {
                            value = arg.Substring(n + 1);
                        }
                    }
                    else {
                        name = arg.Substring(1).ToLower();
                    }
                    if (value != null) {
                        value = value.Trim();
                        value = RemoveQuotes(value);
                        value = value.Trim();
                    }

                    bool badArg = false;
                    switch (name) {
                        case "h":
                        case "help":
                        case "?":
                            needHelp = true;
                            break;

#if SINGULARITY
                        case "app":
                            if (value != null) {
                                 infiles.Add(value);
                            }
                            else {
                                badArg = true;
                            }
                            break;
#endif
                        case "apps":
                            if (value != null) {
#if !SINGULARITY
                                badArg = !ReadInfilesFromFile(value, infiles);
#else
                                Console.WriteLine("'apps' not supported");
                                needHelp = true;
                                break;
#endif
                            }
                            else {
                                badArg = true;
                            }
                            break;

                        case "bartok":
                            if (!StringIsNullOrEmpty(value)) {
                                Bartok = value;
                                if (!File.Exists(Bartok)) {
                                    Console.WriteLine("Bartok not found: {0}", Bartok);
                                    badArg = true;
                                }
                            }
                            else {
                                Console.WriteLine("The '/bartok' argument requires a value, but none was provided.");
                                badArg = true;
                            }
                            break;

                        case "linker":
                            if (!StringIsNullOrEmpty(value)) {
                                LinkExecutablePath = value;
                                if (!File.Exists(LinkExecutablePath)) {
                                    Console.WriteLine("Linker not found: " + LinkExecutablePath);
                                    badArg = true;
                                }
                            }
                            else {
                                Console.WriteLine("The '/link' argument requires a value, but none was provided.");
                                badArg = true;
                                break;
                            }
                            break;

                        case "c":
                            DoCodegen = true;
                            DoLinker = false;
                            DoManifest = false;
                            break;

                        case "clean":
                            DoClean = true;
                            break;

                        case "cache":
                            if (value != null) {
                                cacheDirectory = value.TrimEnd('/', '\\') + "\\";
                            }
                            else {
                                badArg = true;
                            }
                            break;

                        case "force":
                            ForceCodegen = true;
                            ForceLinker = true;
                            ForceManifest = true;
                            break;

                        case "libcache":
                            if (value != null) {
#if !SINGULARITY
                                libCacheDirectory = value.TrimEnd('/', '\\') + "\\";
#else
                                libCacheDirectory = value + "/";
                                //Console.WriteLine("Setting libCache to ({0})",libCacheDirectory);
#endif
                            }
                            else {
                                badArg = true;
                            }
                            break;

                        case "link":
                            DoCodegen = false;
                            DoLinker = true;
                            DoManifest = true;
                            break;

                        case "manifest":
                            DoCodegen = false;
                            DoLinker = false;
                            DoManifest = true;
                            break;

                        case "log":
#if !SINGULARITY
                            cacheDirectory = value.TrimEnd('/', '\\') + "\\";
#else
                            cacheDirectory = value + "/";
#endif
                            break;

                        case "native":
                            if (value != null) {
#if  true //!SINGULARITY
                                nativeDirectory = value.TrimEnd('/', '\\') + "\\";
#else
                                nativeDirectory = value + "/";
#endif
                            }
                            else {
                                badArg = true;
                            }
                            break;

                        case "options":
                            if (value != null) {
                                optionsFile = value;
                            }
                            else {
                                badArg = true;
                            }
                            break;

                        case "temp":
                            if (value != null) {
#if  true //!SINGULARITY
                                tempDirectory = value.TrimEnd('/', '\\') + "\\";
#else
                                tempDirectory = value + "/";
#endif
                            }
                            else {
                                badArg = true;
                            }
                            break;

                        case "v":
                            Verbose = true;
                            break;

                        case "par":
                            if (!StringIsNullOrEmpty(value)) {
                                bool force = false;
                                if (value.EndsWith("!")) {
                                    value = value.Substring(0, value.Length - 1);
                                    force = true;
                                }
                                int count;
                                try {
#if !SINGULARITY
                                    count = Convert.ToInt32(value);
#else
                                    count = Int32.Parse(value);
#endif
                                }
                                catch {
                                    badArg = true;
                                    break;
                                }
                                if (count < 1) {
                                    badArg = true;
                                    break;
                                }
                                if (count == 1) {
                                    numWorkerThreads = 1;
                                }
                                else {
                                    if (count > maxWorkerThreads && !force) {
                                        Console.WriteLine("nib: Warning: Cannot use {0} worker threads; this machine only has {1} processors.", count, maxWorkerThreads);
                                        Console.WriteLine("nib: Using {0} worker threads.", maxWorkerThreads);
                                        count = maxWorkerThreads;
                                        numWorkerThreads = count;
                                    }
                                    else {
                                        numWorkerThreads = count;
                                    }
                                }
                            }
                            else {
                                // Parallel builds disabled.
                                numWorkerThreads = 1;
                            }
                            break;

                        case "machine":
                        case "arch":
                        case "architecture":
                            if (value != null) {
                                architecture = value;
                            }
                            else {
                                badArg = true;
                            }
                            break;

                        default:
                            Console.WriteLine("nib: Unknown command line argument: {0}", arg);
                            needHelp = true;
                            break;
                    }
                    if (badArg) {
                        Console.WriteLine("nib: Invalid command line argument: {0}", arg);
                        needHelp = true;
                    }
                }
                else {
                    infiles.Add(arg);
                }
            }

            _BuildingParallel = numWorkerThreads > 1;

            if (architecture == null) {
                Console.WriteLine("No processor architecture specified. Terminating.");
                Usage();
                return -1;
            }

            if (infiles.Count == 0 ||
#if !SINGULARITY
                cacheDirectory == null ||
#endif
                tempDirectory == null) {

                Console.WriteLine("nib: Arguments missing from command line.");
                needHelp = true;
            }

            if (needHelp) {
                Usage();
                return 1;
            }

            // check all input files
            foreach (string filename in infiles) {
                if (!File.Exists(filename)) {
                    Console.WriteLine("nib: error Application manifest '{0}' not found.",
                                      filename);
                    return 2;
                }
            }

            // check all input files
            if (optionsFile != null && !File.Exists(optionsFile)) {
                Console.WriteLine("nib: error Options manifest '{0}' not found.",
                                  optionsFile);
                return 2;
            }
            if (!Directory.Exists(nativeDirectory)) {
                Console.WriteLine("nib: error Native directory '{0}' not found.",
                                  nativeDirectory);
                return 2;
            }
#if !SINGULARITY
            if (!Directory.Exists(cacheDirectory)) {
                Console.WriteLine("nib: error Cache directory '{0}' not found.",
                                  cacheDirectory);
                return 2;
            }
#endif
            if (!Directory.Exists(tempDirectory)) {
                Console.WriteLine("nib: error Temp directory '{0}' not found.",
                                  tempDirectory);
                return 2;
            }

            if (!Directory.Exists(libCacheDirectory)) {
                Console.WriteLine("nib: error libCache directory '{0}' not found.",
                                  libCacheDirectory);
                return 2;
            }

            foreach (string filename in infiles) {
                QueueJob job = new QueueJob();
                job.Filename = filename;
                _WaitingJobQueue.Enqueue(job);
            }

#if !SINGULARITY
            if (_BuildingParallel) {
                if (Kernel32.IsCurrentProcessInJob()) {
                    Console.WriteLine("nib: Current process is already in a job.  Accounting disabled.");
                    _JobHandle = IntPtr.Zero;
                }
                else {
                    try {
                        IntPtr job = Kernel32.CreateJobObject();
                        _JobHandle = job;
                    }
                    catch (Exception ex) {
                        ShowException(ex);
                        _JobHandle = IntPtr.Zero;
                    }
                }
            }
#else
        Console.WriteLine(" call to IsCurrentProcessJob");
#endif
            int worker_count = numWorkerThreads;

            if (worker_count > 1) {
                Console.WriteLine("nib: Using {0} processors.", worker_count);
            }
            else {
                // Console.WriteLine("nib: Using 1 processor.");
            }

            WorkerThread[] workers = new WorkerThread[worker_count];
            for (int i = 0; i < worker_count; i++) {
                NativeImageBuilder nib = new NativeImageBuilder(cacheDirectory,
                                                                libCacheDirectory,
                                                                nativeDirectory,
                                                                tempDirectory);

                if (optionsFile != null && !nib.LoadOptionsManifest(optionsFile)) {
                    return 3;
                }

                if (_BuildingParallel) {
                    nib._PrintPrefix = String.Format("{0}> ", i);
                    nib._ChildProcessorAffinityMask = (IntPtr)(1 << i);
                }
                else {
                    nib._PrintPrefix = "";
                    nib._ChildProcessorAffinityMask = IntPtr.Zero;
                }

                WorkerThread worker = new WorkerThread(nib, i);
                Thread thread = new Thread(new ThreadStart(worker.ThreadMain));
#if !SINGULARITY
                thread.IsBackground = true;
#endif
                worker.Thread = thread;

                workers[i] = worker;
            }

            // Console.WriteLine("nib: starting worker threads...");

            for (int i = 0; i < worker_count; i++) {
                workers[i].Thread.Start();
            }

            // Console.WriteLine("nib: waiting for all worker threads...");

            for (int i = 0; i < worker_count; i++) {
                Thread thread = workers[i].Thread;
                thread.Join();
            }

            TimeSpan elapsed = DateTime.Now - Began;

            // const string times_format_uk = "nib: {0,-30} : {1,-16} total = {2,-16} user + {3,-16} kernel";
            const string times_format    = "nib: {0,-32} : {1}";

#if !SINGULARITY
            if (_BuildingParallel) {
                if (_JobHandle != IntPtr.Zero) {
                    try {
                        JOBOBJECT_BASIC_ACCOUNTING_INFORMATION job_info;
                        job_info = Kernel32.QueryJobObjectBasicAccountingInformation(_JobHandle);

                        TimeSpan total_job_time = job_info.TotalUserTime + job_info.TotalKernelTime;

                        Console.WriteLine(
                            times_format,
                            "CPU time consumed by jobs",
                            total_job_time);

                        Console.WriteLine(
                            times_format,
                            "    in user mode",
                            job_info.TotalUserTime);

                        Console.WriteLine(
                            times_format,
                            "    in kernel mode",
                            job_info.TotalKernelTime);

                        Console.WriteLine(
                            times_format,
                            "Elapsed time (wall time)",
                            elapsed);

                        Console.WriteLine("nib:");
                        if (elapsed.TotalMilliseconds > 0) {
                            double elapsed_ms = elapsed.TotalMilliseconds;
                            double total_job_ms = total_job_time.TotalMilliseconds;
                            double throughput_ratio = total_job_ms / elapsed_ms;
                            double throughput_percent = Math.Round(throughput_ratio * 100.0);
                            Console.WriteLine("nib:      Parallel throughput ratio: {0}%", throughput_percent);
                        }
                        else {
                            Console.WriteLine("nib:      Too little time elapsed to compute ratio.");
                        }
                    }
                    catch (Exception ex) {
                        Console.WriteLine("FAILED to query job info!");
                        ShowException(ex);
                    }
                }
                else {
                    Console.WriteLine("nib: Child process times not available.");
                }
            }
#else
            Console.WriteLine("nib: Child process times not available.");
            Console.WriteLine(times_format, "Elapsed time (wall time)", elapsed);
#endif
            int succeeded = 0;
            int failed = 0;
            foreach (QueueJob job in _FinishedJobList) {
                if (job.Result) {
                    succeeded++;
                }
                else {
                    failed++;
                }
            }

            if (_FinishedJobList.Count != infiles.Count) {
                // This is defensive check.  Previously worker
                // threads could raise and exception and leave nib
                // running to completion without placing job on
                // _FinishedJobList.
                failed += infiles.Count - _FinishedJobList.Count;
            }
#if !SINGULARITY
            if (_JobHandle != IntPtr.Zero) {
                Kernel32.CloseHandle(_JobHandle);
            }
#endif
            if (infiles.Count > 1) {
                Console.WriteLine("nib:      {0} job(s) succeeded, {1} job(s) failed", succeeded, failed);
            }

            return (failed != 0) ? 4 : 0;
        }
    }
}
