コード例 #1
0
ファイル: BuildHistory.cs プロジェクト: MikePopoloski/ampere
        public void BuildSucceeded(BuildInstance instance)
        {
            // build succeeded; create/update the history entry
            var entry = new HistoryEntry();
            entry.Byproducts = instance.Byproducts.Select(b => b.ToLower()).ToSet();

            var node = instance.Pipeline;
            while (node != null)
            {
                entry.StageTypes.Add(node.GetType());
                entry.StageHashes.Add(node.Hash());

                node = node.InputNode;
            }

            bool hashInputs = (instance.Env.InputChangeDetection & ChangeDetection.Hash) != 0;
            foreach (var input in instance.Inputs)
                entry.InputCache.Add(CreateFileEntry(input, hashInputs));

            bool hashOutputs = (instance.Env.OutputChangeDetection & ChangeDetection.Hash) != 0;
            entry.OutputCache.Add(CreateFileEntry(instance.OutputPath, hashOutputs));
            foreach (var output in instance.Byproducts)
                entry.OutputCache.Add(CreateFileEntry(output, hashOutputs));

            // add dependent builds
            entry.Dependencies.AddRange(instance.Dependencies);

            // create entries for each dependent temp build
            foreach (var build in instance.TempBuilds)
                entry.TempDependencies.Add(CreateTempEntry(build));

            history.AddOrUpdate(instance.OutputName.ToLower(), entry, (k, h) => entry);
        }
コード例 #2
0
ファイル: TestProcessor.cs プロジェクト: MikePopoloski/ampere
        public IEnumerable<object> Run(BuildInstance instance, IEnumerable<object> inputs)
        {
            // parse the material as XML
            var stream = inputs.First() as Stream;
            if (stream == null)
            {
                instance.Log(LogLevel.Error, "Inputs to TestParser must be a single Stream object.");
                return null;
            }

            var document = XDocument.Load(stream);
            var root = document.Element("Test");
            if (root == null)
            {
                instance.Log(LogLevel.Error, "Test file must start with a <Test> node.");
                return null;
            }

            string data = null;
            var dataElement = root.Element("Data");
            if (dataElement != null)
                data = dataElement.Value;

            // example of finding all "dependencies" of this asset and kicking off builds for them
            var dependencies = root.Element("Dependencies");
            if (dependencies != null)
            {
                foreach (var element in dependencies.Elements())
                    instance.Start((string)element.Attribute("Name"));
            }

            // example of a "temporary" build, one that you can embed the results into this asset
            var embed = (string)root.Attribute("Embed");
            string embeddedData = null;

            if (embed != null)
            {
                var tempBuild = instance.StartTemp(embed);
                if (tempBuild == null)  // some error ocurred while trying to build it
                    return null;

                var tempStream = tempBuild.Results.First() as Stream;
                embeddedData = string.Concat(ToByteArray(tempStream));
            }

            var output = new MemoryStream();
            var writer = new StreamWriter(output) { AutoFlush = true };
            writer.WriteLine("This is my asset data! Woohoo!");
            if (data != null)
                writer.WriteLine(data);
            if (embeddedData != null)
                writer.WriteLine("Some embedded data: " + embeddedData);

            return new[] { output };
        }
コード例 #3
0
ファイル: ProcessorNode.cs プロジェクト: MikePopoloski/ampere
 public override IEnumerable<object> Evaluate(BuildInstance instance, IEnumerable<object> inputs)
 {
     try
     {
         return Processor(instance, inputs);
     }
     catch (Exception e)
     {
         instance.Log.Error(e.ToString());
         return null;
     }
 }
コード例 #4
0
ファイル: InputNode.cs プロジェクト: MikePopoloski/ampere
 public override IEnumerable<object> Evaluate(BuildInstance instance, IEnumerable<object> unused)
 {
     // open up the filestreams
     try
     {
         return instance.Inputs.Select(p => File.OpenRead(p)).ToArray();
     }
     catch (IOException e)
     {
         instance.Log.Error(e.Message);
         return null;
     }
 }
コード例 #5
0
ファイル: OutputNode.cs プロジェクト: MikePopoloski/ampere
        public override IEnumerable<object> Evaluate(BuildInstance instance, IEnumerable<object> inputs)
        {
            // figure out the final names of each output
            var outputs = new List<string>();
            outputs.Add(instance.OutputPath);
            outputs.AddRange(instance.Byproducts);

            // make sure we have enough inputs to satisfy each output
            var inputArray = inputs.ToArray();
            if (inputArray.Length != outputs.Count)
            {
                instance.Log.Error("Number of inputs does not match number of outputs for '{0}' (line {1}).", instance.OutputName, LineNumber);
                return null;
            }

            instance.Results = inputs;
            if (instance.IsTempBuild && !instance.Env.WriteTempBuilds)
                return null;

            // match each input to an output name
            for (int i = 0; i < inputArray.Length; i++)
            {
                // if we have a filestream, we can do a straight file copy because we know it hasn't been changed
                var outputPath = outputs[i];
                var stream = inputArray[i] as Stream;
                if (stream == null)
                    return null;

                var file = stream as FileStream;
                if (file != null)
                    File.Copy(file.Name, outputPath, true);
                else if (stream == null)
                {
                    instance.Log.Error("Inputs to Build() node must all be of type stream ('{0}' on line {1}).", instance.OutputName, LineNumber);
                    return null;
                }
                else
                {
                    // otherwise, write to file
                    stream.Seek(0, SeekOrigin.Begin);
                    using (var outputStream = File.Create(outputPath))
                        stream.CopyTo(outputStream);
                }

                stream.Close();
            }

            return Enumerable.Empty<Stream>();
        }
コード例 #6
0
ファイル: InputNode.cs プロジェクト: MikePopoloski/ampere
        public bool ResolveNames(BuildInstance instance)
        {
            // resolve input names into full paths; if any fail, an error occurs
            var paths = new List<string>();
            foreach (var input in inputs)
            {
                var fullName = instance.Match.Result(input);
                var path = instance.Env.ResolveInput(fullName);
                if (string.IsNullOrEmpty(path))
                {
                    instance.Log.Error("Could not resolve input '{0}' (line {1}).", fullName, LineNumber);
                    return false;
                }

                paths.Add(path);
            }

            instance.Inputs = paths.ToArray();
            return true;
        }
コード例 #7
0
ファイル: BuildNode.cs プロジェクト: MikePopoloski/ampere
 public abstract IEnumerable<object> Evaluate(BuildInstance instance, IEnumerable<object> inputs);
コード例 #8
0
ファイル: BuildContext.cs プロジェクト: MikePopoloski/ampere
        BuildInstance InternalStart(string name, OutputNode rule, BuildInstance instance)
        {
            // walk down the pipeline and build from the bottom-up
            var currentStage = rule.GetBottomNode();
            var inputNode = currentStage as InputNode;

            if (!inputNode.ResolveNames(instance) || !rule.ResolveNames(instance))
                return BuildFailed(name, instance);

            // check to see if we even need to do this build
            if (!FullRebuild && !history.ShouldBuild(instance))
            {
                allAssets.Add(name);
                foreach (var entry in history.GetDependencies(instance.OutputName))
                    allAssets.Add(entry);

                Log.Info("Skipping '{0}' (up-to-date).", name);
                instance.Status = BuildStatus.Skipped;
                Interlocked.Increment(ref Stats.Skipped);
                return instance;
            }

            // run the pipeline
            IEnumerable<object> state = null;
            while (currentStage != null)
            {
                // run the current stage, saving the results and passing them on to the next stage in the pipeline
                try
                {
                    state = currentStage.Evaluate(instance, state);
                }
                catch (Exception e)
                {
                    Log.Error("Exception thrown while building '{0}': {1}", name, e);
                    return BuildFailed(name, instance);
                }

                if (state == null)
                {
                    if (instance.IsTempBuild && currentStage is OutputNode)
                        return instance;

                    return BuildFailed(name, instance);
                }

                currentStage = currentStage.OutputNode;
            }

            if (instance.Status == BuildStatus.Failed)
                return BuildFailed(name, instance);

            history.BuildSucceeded(instance);
            builtAssets.Add(instance.OutputName);
            allAssets.Add(instance.OutputName);
            foreach (var byproduct in instance.Byproducts)
            {
                allAssets.Add(byproduct);
                builtAssets.Add(byproduct);
            }

            Log.Write("Build for '{0}' successful.", name);
            instance.Status = BuildStatus.Succeeded;
            Interlocked.Increment(ref Stats.Succeeded);
            return instance;
        }
コード例 #9
0
ファイル: BuildContext.cs プロジェクト: MikePopoloski/ampere
 BuildInstance BuildFailed(string name, BuildInstance instance)
 {
     history.BuildFailed(instance);
     Log.Error("FAILED! Build for '{0}'", name);
     instance.Status = BuildStatus.Failed;
     Interlocked.Increment(ref Stats.Failed);
     return instance;
 }
コード例 #10
0
ファイル: BuildContext.cs プロジェクト: MikePopoloski/ampere
        public Task<BuildInstance> Start(string name, bool tempBuild = false)
        {
            // find best applicable rule
            var best = rules
                .Select(r => new { Node = r, Match = r.Match(name) })
                .Where(m => m.Match.Success)
                .GroupBy(m => m.Node.Priority)
                .OrderBy(g => g.Key)
                .FirstOrDefault();

            if (best == null)
            {
                Log.Error("No applicable rule found for asset '{0}'.", name);
                Interlocked.Increment(ref Stats.Failed);
                return null;
            }
            else if (best.Count() != 1)
                Log.Warning("More than one rule with the same priority matches asset '{0}' (rules on lines: {1})", name, string.Join(", ", best.Select(b => b.Node.LineNumber)));

            // we've found the rule we will use. queue up the task to build the asset, or return the current one if it's already being built
            var chosen = best.First();
            var task = runningBuilds.GetOrAdd(name, new Lazy<Task<BuildInstance>>(() =>
            {
                var instance = new BuildInstance(this, chosen.Match, chosen.Node, tempBuild);
                var job = Task.Run(() => InternalStart(name, chosen.Node, instance));
                job.ContinueWith(t => runningBuilds.Remove(name));
                return job;
            })).Value;

            // also register the task for any byproducts
            foreach (var byproduct in chosen.Node.Byproducts)
            {
                var byproductName = chosen.Match.Result(byproduct);
                runningBuilds.TryAdd(byproductName, new Lazy<Task<BuildInstance>>(() => task));
            }

            return task;
        }
コード例 #11
0
ファイル: BuildHistory.cs プロジェクト: MikePopoloski/ampere
 public void BuildFailed(BuildInstance instance)
 {
     // build failed; remove the history entry to force a rebuild.
     HistoryEntry entry;
     history.TryRemove(instance.OutputName.ToLower(), out entry);
 }
コード例 #12
0
ファイル: BuildHistory.cs プロジェクト: MikePopoloski/ampere
        TempEntry CreateTempEntry(BuildInstance instance)
        {
            var entry = new TempEntry();
            bool hashInputs = (instance.Env.InputChangeDetection & ChangeDetection.Hash) != 0;
            foreach (var input in instance.Inputs)
            {
                entry.Inputs.Add(CreateFileEntry(input, hashInputs));
                entry.InputPaths.Add(input);
            }

            // create entries for each dependent temp build
            foreach (var build in instance.TempBuilds)
                entry.TempDependencies.Add(CreateTempEntry(build));

            return entry;
        }
コード例 #13
0
ファイル: BuildHistory.cs プロジェクト: MikePopoloski/ampere
        public bool ShouldBuild(BuildInstance instance)
        {
            // do comparisons in order from cheapest to most expensive to try to early out when a change is obvious
            // check 1: see if we have history for this output
            HistoryEntry entry;
            if (!history.TryGetValue(instance.OutputName.ToLower(), out entry))
                return true;

            // check 3: make sure the byproducts match
            var byproductSet = instance.Byproducts.Select(b => b.ToLower()).ToSet();
            if (!byproductSet.SetEquals(entry.Byproducts))
                return true;

            // check 4: compare number and type of pipeline stages
            var node = instance.Pipeline;
            for (int i = 0; i < entry.StageTypes.Count; i++)
            {
                if (node == null || node.GetType() != entry.StageTypes[i])
                    return true;

                node = node.InputNode;
            }

            // any nodes left over mean that the pipeline was changed
            if (node != null)
                return true;

            // check 5: check for pipeline processor changes
            node = instance.Pipeline;
            foreach (var stage in entry.StageHashes)
            {
                if (stage != node.Hash())
                    return true;

                node = node.InputNode;
            }

            // check 6: changes in inputs
            if (instance.Env.InputChangeDetection != ChangeDetection.None)
            {
                for (int i = 0; i < instance.Inputs.Length; i++)
                {
                    if (CheckChanged(instance.Env.InputChangeDetection, new FileInfo(instance.Inputs[i]), entry.InputCache[i]))
                        return true;
                }
            }

            // check 7: changes in outputs
            if (instance.Env.OutputChangeDetection != ChangeDetection.None)
            {
                if (CheckChanged(instance.Env.OutputChangeDetection, new FileInfo(instance.OutputPath), entry.OutputCache[0]))
                    return true;

                for (int i = 0; i < instance.Byproducts.Length; i++)
                {
                    if (CheckChanged(instance.Env.OutputChangeDetection, new FileInfo(instance.Byproducts[i]), entry.OutputCache[i + 1]))
                        return true;
                }
            }

            // check 8: look at any dependent temp builds and see if they have been changed
            if (instance.Env.InputChangeDetection != ChangeDetection.None)
            {
                if (entry.TempDependencies.Any(t => TempInputsHaveChanged(t, instance.Env.InputChangeDetection)))
                    return true;
            }

            // at this point, we can safely say that the entire pipeline is the same. no need to do a build
            // however, some of our dependencies may have changed, so let them sort themselves out
            foreach (var dependency in entry.Dependencies)
                context.Start(dependency);

            return false;
        }
コード例 #14
0
ファイル: OutputNode.cs プロジェクト: MikePopoloski/ampere
        public bool ResolveNames(BuildInstance instance)
        {
            var paths = new List<string>();
            foreach (var output in Byproducts.Select(b => instance.Match.Result(b)))
            {
                var outputPath = instance.IsTempBuild ?
                    instance.Env.ResolveTemp(output) :
                    instance.Env.ResolveOutput(output);

                if (string.IsNullOrEmpty(outputPath))
                {
                    instance.Log.Error("Could not resolve output '{0}' (line {1}).", output, LineNumber);
                    return false;
                }

                paths.Add(outputPath);
            }

            instance.OutputPath = instance.IsTempBuild ? instance.Env.ResolveTemp(instance.OutputName) : instance.Env.ResolveOutput(instance.OutputName);
            instance.Byproducts = paths.ToArray();

            return !string.IsNullOrEmpty(instance.OutputPath);
        }
コード例 #15
0
ファイル: RunNode.cs プロジェクト: MikePopoloski/ampere
 internal ArgProvider(BuildInstance instance, IEnumerable<object> inputs, int line)
 {
     this.instance = instance;
     this.inputs = inputs.ToArray();
     this.lineNumber = line;
 }
コード例 #16
0
ファイル: ExternalNode.cs プロジェクト: MikePopoloski/ampere
        public override IEnumerable<object> Evaluate(BuildInstance instance, IEnumerable<object> inputs)
        {
            if (!File.Exists(fileName))
            {
                instance.Log.Error("Could not find external program '{0}'. (line {1})", fileName, LineNumber);
                return null;
            }

            if (resultProviders.Count == 0)
            {
                instance.Log.Error("Running an external tool requires at least one Result specifier. (line {0})", LineNumber);
                return null;
            }

            // perform argument replacement
            var argProvider = new ArgProvider(instance, inputs, LineNumber);
            string currentArguments = instance.Match.Result(argumentFormat);
            currentArguments = string.Format(currentArguments, argProviders.Select(p => p(argProvider)).ToArray());

            var startInfo = new ProcessStartInfo(fileName, currentArguments);
            startInfo.CreateNoWindow = true;
            startInfo.UseShellExecute = false;
            startInfo.RedirectStandardOutput = (options & RunOptions.RedirectOutput) != 0;
            startInfo.RedirectStandardError = (options & RunOptions.RedirectError) != 0;

            var process = new Process();
            process.StartInfo = startInfo;
            process.EnableRaisingEvents = true;
            process.OutputDataReceived += (o, e) =>
            {
                if (!string.IsNullOrEmpty(e.Data))
                    instance.Log.Info(e.Data);
            };

            process.ErrorDataReceived += (o, e) =>
            {
                if (!string.IsNullOrEmpty(e.Data))
                    instance.Log.Error(e.Data);
            };

            process.Start();

            if (startInfo.RedirectStandardOutput)
                process.BeginOutputReadLine();

            if (startInfo.RedirectStandardError)
                process.BeginErrorReadLine();

            process.WaitForExit();

            if ((options & RunOptions.DontCheckResultCode) == 0 && process.ExitCode != 0)
            {
                instance.Log.Error("Running tool '{0}' failed with result code {1}. (line {2})", fileName, process.ExitCode, LineNumber);
                return null;
            }

            var results = new List<Stream>();
            foreach (var output in resultProviders.Select(p => p(argProvider)))
            {
                string path = instance.Match.Result(output);
                results.Add(File.OpenRead(path));
            }

            return results;
        }