The Labs \ Source Viewer \ SSCLI \ Microsoft.Tools.Build \ MergeAttributes

  1. //------------------------------------------------------------------------------
  2. // <copyright file="mergeattributes.cs" company="Microsoft">
  3. //
  4. // Copyright (c) 2006 Microsoft Corporation. All rights reserved.
  5. //
  6. // The use and distribution terms for this software are contained in the file
  7. // named license.txt, which can be found in the root of this distribution.
  8. // By using this software in any fashion, you are agreeing to be bound by the
  9. // terms of this license.
  10. //
  11. // You must not remove this notice, or any other, from this software.
  12. //
  13. // </copyright>
  14. //------------------------------------------------------------------------------
  15. /// <devdoc>
  16. /// This is a small console application that merges the assembly attributes from
  17. /// two different files. Because this is part of our build process, you can't
  18. /// link to any Fx dlls!
  19. /// </devdoc>
  20. namespace Microsoft.Tools.Build
  21. {
  22.     using System;
  23.     using System.Collections;
  24.     using System.IO;
  25.     using System.Globalization;
  26.    
  27.     /// <include file='doc\mergeattributes.uex' path='docs/doc[@for="MergeAttributes"]/*' />
  28.     public class MergeAttributes
  29.     {
  30.        
  31.         /// <devdoc>
  32.         /// Adds attributes from the given file to the attribute hashtable.
  33.         /// </devdoc>
  34.         private static void AddFileAttributes(StreamReader file, Hashtable attributes)
  35.         {
  36.             string contents = file.ReadToEnd();
  37.             int startBlock;
  38.             int endBlock;
  39.             LocateAttributeBlock(contents, out startBlock, out endBlock);
  40.             string attributeBlock = contents.Substring(startBlock, endBlock - startBlock);
  41.             ParseFileAttributes(attributeBlock, attributes);
  42.         }
  43.        
  44.         /// <devdoc>
  45.         /// Writes out the merged file data to a stream.
  46.         /// </devdoc>
  47.         private static void EmitMergedFile(TextWriter stream, string beforeBlob, Hashtable attributes, string afterBlob)
  48.         {
  49.            
  50.             stream.Write(beforeBlob);
  51.            
  52.             foreach (DictionaryEntry de in attributes) {
  53.                 stream.Write((string)de.Key);
  54.                 if (de.Value != null) {
  55.                     stream.Write((string)de.Value);
  56.                 }
  57.             }
  58.            
  59.             stream.Write(afterBlob);
  60.         }
  61.        
  62.         /// <devdoc>
  63.         /// Throws a file error
  64.         /// </devdoc>
  65.         private static void ErrorFileNotExist(string fileName)
  66.         {
  67.             throw new Exception("The file " + fileName + " does not exist.");
  68.         }
  69.        
  70.         /// <devdoc>
  71.         /// Throws a file error
  72.         /// </devdoc>
  73.         private static void ErrorInvalidFile()
  74.         {
  75.             throw new Exception("The file does not conform to the proper format.");
  76.         }
  77.        
  78.         /// <devdoc>
  79.         /// Throws a usage error
  80.         /// </devdoc>
  81.         private static void ErrorUsage()
  82.         {
  83.            
  84.             throw new Exception("Usage: MergeAttributes <primary file> <merge file> [-exclude <metadata key>] [-out <file>]\r\n" + " primary file - The file to merge attributes into\r\n" + " merge file - The file that contains attributes to merge\r\n" + " -exclude - Zero or more metadata keys to exclude from the merge. Typically\r\n" + " you would exclude the .ver key to preserve the original\r\n" + " assembly's version.\r\n" + " -out - If specified, an output file to route to. If not\r\n" + " specified the output is routed to stdout.\r\n");
  85.         }
  86.        
  87.         /// <devdoc>
  88.         /// Given a string of text this locates the inner attribute block for the assembly.
  89.         /// </devdoc>
  90.         private static void LocateAttributeBlock(string contents, out int startBlock, out int endBlock)
  91.         {
  92.            
  93.            
  94.             int startIndex = 0;
  95.             int index;
  96.             string searchString = ".assembly";
  97.             char[] delims = new char[] {' '};
  98.            
  99.             startBlock = -1;
  100.             // sentinel so we know if we found it.
  101.             endBlock = -1;
  102.            
  103.             while ((index = contents.IndexOf(searchString, startIndex)) != -1) {
  104.                
  105.                 // Grab the end of line and then parse the line.
  106.                 //
  107.                 int endLineIndex = contents.IndexOf('\n', index);
  108.                 string line = contents.Substring(index, endLineIndex - index);
  109.                 string[] tokens = line.Split(delims);
  110.                
  111.                 // If we've found the right block, tokens will have two elements
  112.                 // and the second element won't be "extern".
  113.                 //
  114.                 if (tokens.Length == 2 && !tokens[1].Equals("extern")) {
  115.                     // This is our location. Advance until we find the starting
  116.                     // "{"
  117.                     for (int i = endLineIndex; i < contents.Length; i++) {
  118.                         if (contents[i] == '{') {
  119.                             while (++i < contents.Length && contents[i] != '\n')
  120.                                 ;
  121.                             startBlock = i + 1;
  122.                             break;
  123.                         }
  124.                     }
  125.                    
  126.                     if (startBlock == -1) {
  127.                         ErrorInvalidFile();
  128.                     }
  129.                    
  130.                     // Now advance until we find the ending point.
  131.                     //
  132.                     endBlock = contents.IndexOf("}", startBlock);
  133.                    
  134.                     if (endBlock == -1) {
  135.                         ErrorInvalidFile();
  136.                     }
  137.                    
  138.                     break;
  139.                 }
  140.                
  141.                 // Now advance to the next index.
  142.                 //
  143.                 startIndex = index + searchString.Length;
  144.                 if (startIndex >= contents.Length) {
  145.                     break;
  146.                 }
  147.             }
  148.         }
  149.        
  150.         /// <include file='doc\mergeattributes.uex' path='docs/doc[@for="MergeAttributes.Main"]/*' />
  151.         /// <devdoc>
  152.         /// Main entry point.
  153.         /// </devdoc>
  154.         public static int Main(string[] args)
  155.         {
  156.            
  157.             int returnCode = 0;
  158.            
  159.             StreamReader primaryFile = null;
  160.             StreamReader mergeFile = null;
  161.             TextWriter outFile = null;
  162.             bool closeOutFile = false;
  163.            
  164.             try {
  165.                 string primaryFileName;
  166.                 string mergeFileName;
  167.                 string outFileName;
  168.                 ArrayList excludeList;
  169.                
  170.                 // Parse the command line.
  171.                 //
  172.                 ParseArguments(args, out primaryFileName, out mergeFileName, out excludeList, out outFileName);
  173.                
  174.                 // Open the files.
  175.                 //
  176.                 primaryFile = new StreamReader(File.OpenRead(primaryFileName));
  177.                 mergeFile = new StreamReader(File.OpenRead(mergeFileName));
  178.                 if (outFileName != null) {
  179.                     if (File.Exists(outFileName)) {
  180.                         File.Delete(outFileName);
  181.                     }
  182.                     outFile = new StreamWriter(File.OpenWrite(outFileName));
  183.                     closeOutFile = true;
  184.                 }
  185.                 else {
  186.                     outFile = Console.Out;
  187.                 }
  188.                
  189.                 // We use a hashtable to map assembly attributes to their assignments.
  190.                 // We also keep track of "before" and "after" blobs of text for the primary file
  191.                 // so we can easily reconstruct the file.
  192.                 //
  193.                 string beforeBlob;
  194.                 string afterBlob;
  195.                 Hashtable attributes = new Hashtable();
  196.                 Hashtable excludeAttributes = null;
  197.                
  198.                 ParsePrimaryFile(primaryFile, out beforeBlob, attributes, out afterBlob);
  199.                
  200.                 if (excludeList != null) {
  201.                     excludeAttributes = new Hashtable(excludeList.Count);
  202.                     foreach (string s in excludeList) {
  203.                         if (attributes.ContainsKey(s)) {
  204.                             excludeAttributes[s] = attributes[s];
  205.                         }
  206.                     }
  207.                 }
  208.                
  209.                 AddFileAttributes(mergeFile, attributes);
  210.                
  211.                 // Now, if we got a set of excluded attributes, re-integrate
  212.                 // them.
  213.                 //
  214.                 if (excludeAttributes != null) {
  215.                     foreach (DictionaryEntry de in excludeAttributes) {
  216.                         attributes[de.Key] = de.Value;
  217.                     }
  218.                 }
  219.                
  220.                 EmitMergedFile(outFile, beforeBlob, attributes, afterBlob);
  221.                
  222.                 outFile.Flush();
  223.             }
  224.             catch (Exception ex) {
  225.                 string message = ex.Message;
  226.                 if (message == null) {
  227.                     message = ex.ToString();
  228.                 }
  229.                
  230.                 Console.Error.WriteLine(message);
  231.                 returnCode = -1;
  232.             }
  233.            
  234.             if (primaryFile != null)
  235.                 primaryFile.Close();
  236.             if (mergeFile != null)
  237.                 mergeFile.Close();
  238.             if (closeOutFile)
  239.                 outFile.Close();
  240.            
  241.             return returnCode;
  242.         }
  243.        
  244.         /// <devdoc>
  245.         /// Parses our command line.
  246.         /// </devdoc>
  247.         private static void ParseArguments(string[] args, out string primaryFile, out string mergeFile, out ArrayList excludeList, out string outFile)
  248.         {
  249.            
  250.             primaryFile = null;
  251.             mergeFile = null;
  252.             outFile = null;
  253.             excludeList = null;
  254.            
  255.             for (int i = 0; i < args.Length; i++) {
  256.                 string arg = args[i];
  257.                
  258.                 if (arg[0] == '-' || arg[0] == '/') {
  259.                     string option = arg.Substring(1).ToLower(CultureInfo.InvariantCulture);
  260.                     if (option.Equals("out")) {
  261.                         if (outFile != null || ++i == args.Length) {
  262.                             ErrorUsage();
  263.                         }
  264.                        
  265.                         outFile = args[i];
  266.                     }
  267.                     else if (option.Equals("exclude")) {
  268.                         if (++i == args.Length) {
  269.                             ErrorUsage();
  270.                         }
  271.                         if (excludeList == null) {
  272.                             excludeList = new ArrayList();
  273.                         }
  274.                         excludeList.Add(args[i]);
  275.                     }
  276.                     else {
  277.                         ErrorUsage();
  278.                     }
  279.                 }
  280.                 else {
  281.                     if (primaryFile == null) {
  282.                         primaryFile = arg;
  283.                     }
  284.                     else if (mergeFile == null) {
  285.                         mergeFile = arg;
  286.                     }
  287.                     else {
  288.                         ErrorUsage();
  289.                     }
  290.                 }
  291.             }
  292.            
  293.             // We should have a primary file and a merge file
  294.             //
  295.             if (primaryFile == null || mergeFile == null) {
  296.                 ErrorUsage();
  297.             }
  298.            
  299.             // And both input files must exist
  300.             //
  301.             if (!File.Exists(primaryFile)) {
  302.                 ErrorFileNotExist(primaryFile);
  303.             }
  304.            
  305.             if (!File.Exists(mergeFile)) {
  306.                 ErrorFileNotExist(mergeFile);
  307.             }
  308.         }
  309.        
  310.         /// <devdoc>
  311.         /// Takes the given block of text and parses attributes from it.
  312.         /// </devdoc>
  313.         private static void ParseFileAttributes(string block, Hashtable attributes)
  314.         {
  315.            
  316.             // Search for the beginning of an attribute
  317.             for (int idx = 0; idx < block.Length; idx++) {
  318.                 if (block[idx] == '.') {
  319.                    
  320.                     // Got an attribute. Now parse it. If the attribute starts with
  321.                     // "hash" or "ver", read one word and use that as the key. If not,
  322.                     // then read to an equal sign and use that as the key.
  323.                    
  324.                     int endIdx = block.IndexOf(' ', idx + 1);
  325.                     if (endIdx == -1) {
  326.                         ErrorInvalidFile();
  327.                     }
  328.                    
  329.                     string token = block.Substring(idx, endIdx - idx);
  330.                     if (token.Equals(".hash") || token.Equals(".ver")) {
  331.                        
  332.                         // Special token. Parse to the end of the line.
  333.                         //
  334.                         int eolIdx = block.IndexOf('\n', endIdx + 1);
  335.                         if (eolIdx == -1) {
  336.                             eolIdx = block.Length - 1;
  337.                         }
  338.                        
  339.                         string value = block.Substring(endIdx, eolIdx - endIdx + 1);
  340.                         attributes[token] = value;
  341.                         idx = eolIdx;
  342.                     }
  343.                     else {
  344.                        
  345.                         // Normal token. Locate the "=" and then parse to the end of the ")"
  346.                         //
  347.                         int eqIdx = block.IndexOf('=', endIdx + 1);
  348.                         if (eqIdx == -1) {
  349.                             ErrorInvalidFile();
  350.                         }
  351.                        
  352.                         // Normal token is up to the equals
  353.                         //
  354.                         token = block.Substring(idx, eqIdx - idx);
  355.                        
  356.                         // Must be sensitive to end of line comments.
  357.                         //
  358.                         int parenIdx = -1;
  359.                        
  360.                         for (int i = eqIdx + 1; i < block.Length; i++) {
  361.                             if (block[i] == '/' && i + 1 < block.Length && block[i + 1] == '/') {
  362.                                 // comment. Skip to the end of the line.
  363.                                 while (i < block.Length && block[i] != '\n') {
  364.                                     i++;
  365.                                 }
  366.                                 i++;
  367.                                 if (i >= block.Length) {
  368.                                     break;
  369.                                 }
  370.                             }
  371.                            
  372.                             if (block[i] == ')') {
  373.                                 parenIdx = i;
  374.                                 break;
  375.                             }
  376.                         }
  377.                        
  378.                         if (parenIdx == -1) {
  379.                             ErrorInvalidFile();
  380.                         }
  381.                        
  382.                         int eolIdx = block.IndexOf('\n', parenIdx + 1);
  383.                         if (eolIdx == -1) {
  384.                             ErrorInvalidFile();
  385.                         }
  386.                        
  387.                         string value = block.Substring(eqIdx, eolIdx - eqIdx + 1);
  388.                         attributes[token] = value;
  389.                         idx = eolIdx;
  390.                     }
  391.                    
  392.                 }
  393.                 else if (block[idx] == '/' && idx < block.Length && block[idx + 1] == '/') {
  394.                     // Advance to the end of the line
  395.                     int eolIdx = block.IndexOf('\n', idx + 1);
  396.                     if (eolIdx != -1) {
  397.                         idx = eolIdx;
  398.                     }
  399.                     else {
  400.                         idx = block.Length;
  401.                     }
  402.                 }
  403.             }
  404.         }
  405.        
  406.         /// <devdoc>
  407.         /// Parses the primary file, adding attributes to the hashtable and
  408.         /// filling in the beginning and ending blobs.
  409.         /// </devdoc>
  410.         private static void ParsePrimaryFile(StreamReader file, out string beforeBlob, Hashtable attributes, out string afterBlob)
  411.         {
  412.             string contents = file.ReadToEnd();
  413.             int startBlock;
  414.             int endBlock;
  415.             LocateAttributeBlock(contents, out startBlock, out endBlock);
  416.             string attributeBlock = contents.Substring(startBlock, endBlock - startBlock);
  417.             ParseFileAttributes(attributeBlock, attributes);
  418.             beforeBlob = contents.Substring(0, startBlock);
  419.             afterBlob = contents.Substring(endBlock);
  420.         }
  421.     }
  422. }

Developer Fusion