|  | @@ -16,7 +16,6 @@
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  #endregion
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -using System;
 | 
	
		
			
				|  |  |  using System.IO;
 | 
	
		
			
				|  |  |  using System.Text;
 | 
	
		
			
				|  |  |  using Microsoft.Build.Framework;
 | 
	
	
		
			
				|  | @@ -55,7 +54,62 @@ namespace Grpc.Tools
 | 
	
		
			
				|  |  |                  && !gsm.EqualNoCase("false");
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        public abstract string[] GetPossibleOutputs(ITaskItem proto);
 | 
	
		
			
				|  |  | +        // Update OutputDir and GrpcOutputDir for the item and all subsequent
 | 
	
		
			
				|  |  | +        // targets using this item. This should only be done if the real
 | 
	
		
			
				|  |  | +        // output directories for protoc should be modified.
 | 
	
		
			
				|  |  | +        public virtual ITaskItem PatchOutputDirectory(ITaskItem protoItem)
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  | +            // Nothing to do
 | 
	
		
			
				|  |  | +            return protoItem;
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        public abstract string[] GetPossibleOutputs(ITaskItem protoItem);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        // Calculate part of proto path relative to root. Protoc is very picky
 | 
	
		
			
				|  |  | +        // about them matching exactly, so can be we. Expect root be exact prefix
 | 
	
		
			
				|  |  | +        // to proto, minus some slash normalization.
 | 
	
		
			
				|  |  | +        protected static string GetRelativeDir(string root, string proto, TaskLoggingHelper log)
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  | +            string protoDir = Path.GetDirectoryName(proto);
 | 
	
		
			
				|  |  | +            string rootDir = EndWithSlash(Path.GetDirectoryName(EndWithSlash(root)));
 | 
	
		
			
				|  |  | +            if (rootDir == s_dotSlash)
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                // Special case, otherwise we can return "./" instead of "" below!
 | 
	
		
			
				|  |  | +                return protoDir;
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            if (Platform.IsFsCaseInsensitive)
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                protoDir = protoDir.ToLowerInvariant();
 | 
	
		
			
				|  |  | +                rootDir = rootDir.ToLowerInvariant();
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            protoDir = EndWithSlash(protoDir);
 | 
	
		
			
				|  |  | +            if (!protoDir.StartsWith(rootDir))
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                log.LogWarning("Protobuf item '{0}' has the ProtoRoot metadata '{1}' " +
 | 
	
		
			
				|  |  | +                               "which is not prefix to its path. Cannot compute relative path.",
 | 
	
		
			
				|  |  | +                    proto, root);
 | 
	
		
			
				|  |  | +                return "";
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            return protoDir.Substring(rootDir.Length);
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        // './' or '.\', normalized per system.
 | 
	
		
			
				|  |  | +        protected static string s_dotSlash = "." + Path.DirectorySeparatorChar;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        protected static string EndWithSlash(string str)
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  | +            if (str == "")
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                return s_dotSlash;
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            if (str[str.Length - 1] != '\\' && str[str.Length - 1] != '/')
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                return str + Path.DirectorySeparatorChar;
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            return str;
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  |      };
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      // C# generator services.
 | 
	
	
		
			
				|  | @@ -63,23 +117,42 @@ namespace Grpc.Tools
 | 
	
		
			
				|  |  |      {
 | 
	
		
			
				|  |  |          public CSharpGeneratorServices(TaskLoggingHelper log) : base(log) { }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +        public override ITaskItem PatchOutputDirectory(ITaskItem protoItem)
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  | +            var outputItem = new TaskItem(protoItem);
 | 
	
		
			
				|  |  | +            string root = outputItem.GetMetadata(Metadata.ProtoRoot);
 | 
	
		
			
				|  |  | +            string proto = outputItem.ItemSpec;
 | 
	
		
			
				|  |  | +            string relative = GetRelativeDir(root, proto, Log);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            string outdir = outputItem.GetMetadata(Metadata.OutputDir);
 | 
	
		
			
				|  |  | +            string pathStem = Path.Combine(outdir, relative);
 | 
	
		
			
				|  |  | +            outputItem.SetMetadata(Metadata.OutputDir, pathStem);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            // Override outdir if GrpcOutputDir present, default to proto output.
 | 
	
		
			
				|  |  | +            string grpcdir = outputItem.GetMetadata(Metadata.GrpcOutputDir);
 | 
	
		
			
				|  |  | +            if (grpcdir != "")
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                pathStem = Path.Combine(grpcdir, relative);
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            outputItem.SetMetadata(Metadata.GrpcOutputDir, pathStem);
 | 
	
		
			
				|  |  | +            return outputItem;
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |          public override string[] GetPossibleOutputs(ITaskItem protoItem)
 | 
	
		
			
				|  |  |          {
 | 
	
		
			
				|  |  |              bool doGrpc = GrpcOutputPossible(protoItem);
 | 
	
		
			
				|  |  |              var outputs = new string[doGrpc ? 2 : 1];
 | 
	
		
			
				|  |  | -            string basename = Path.GetFileNameWithoutExtension(protoItem.ItemSpec);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | +            string proto = protoItem.ItemSpec;
 | 
	
		
			
				|  |  | +            string basename = Path.GetFileNameWithoutExtension(proto);
 | 
	
		
			
				|  |  |              string outdir = protoItem.GetMetadata(Metadata.OutputDir);
 | 
	
		
			
				|  |  |              string filename = LowerUnderscoreToUpperCamelProtocWay(basename);
 | 
	
		
			
				|  |  |              outputs[0] = Path.Combine(outdir, filename) + ".cs";
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              if (doGrpc)
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  | -                // Override outdir if kGrpcOutputDir present, default to proto output.
 | 
	
		
			
				|  |  |                  string grpcdir = protoItem.GetMetadata(Metadata.GrpcOutputDir);
 | 
	
		
			
				|  |  |                  filename = LowerUnderscoreToUpperCamelGrpcWay(basename);
 | 
	
		
			
				|  |  | -                outputs[1] = Path.Combine(
 | 
	
		
			
				|  |  | -                    grpcdir != "" ? grpcdir : outdir, filename) + "Grpc.cs";
 | 
	
		
			
				|  |  | +                outputs[1] = Path.Combine(grpcdir, filename) + "Grpc.cs";
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |              return outputs;
 | 
	
		
			
				|  |  |          }
 | 
	
	
		
			
				|  | @@ -142,7 +215,7 @@ namespace Grpc.Tools
 | 
	
		
			
				|  |  |              string proto = protoItem.ItemSpec;
 | 
	
		
			
				|  |  |              string filename = Path.GetFileNameWithoutExtension(proto);
 | 
	
		
			
				|  |  |              // E. g., ("foo/", "foo/bar/x.proto") => "bar"
 | 
	
		
			
				|  |  | -            string relative = GetRelativeDir(root, proto);
 | 
	
		
			
				|  |  | +            string relative = GetRelativeDir(root, proto, Log);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              var outputs = new string[doGrpc ? 4 : 2];
 | 
	
		
			
				|  |  |              string outdir = protoItem.GetMetadata(Metadata.OutputDir);
 | 
	
	
		
			
				|  | @@ -151,7 +224,7 @@ namespace Grpc.Tools
 | 
	
		
			
				|  |  |              outputs[1] = fileStem + ".pb.h";
 | 
	
		
			
				|  |  |              if (doGrpc)
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  | -                // Override outdir if kGrpcOutputDir present, default to proto output.
 | 
	
		
			
				|  |  | +                // Override outdir if GrpcOutputDir present, default to proto output.
 | 
	
		
			
				|  |  |                  outdir = protoItem.GetMetadata(Metadata.GrpcOutputDir);
 | 
	
		
			
				|  |  |                  if (outdir != "")
 | 
	
		
			
				|  |  |                  {
 | 
	
	
		
			
				|  | @@ -162,52 +235,5 @@ namespace Grpc.Tools
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |              return outputs;
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        // Calculate part of proto path relative to root. Protoc is very picky
 | 
	
		
			
				|  |  | -        // about them matching exactly, so can be we. Expect root be exact prefix
 | 
	
		
			
				|  |  | -        // to proto, minus some slash normalization.
 | 
	
		
			
				|  |  | -        string GetRelativeDir(string root, string proto)
 | 
	
		
			
				|  |  | -        {
 | 
	
		
			
				|  |  | -            string protoDir = Path.GetDirectoryName(proto);
 | 
	
		
			
				|  |  | -            string rootDir = EndWithSlash(Path.GetDirectoryName(EndWithSlash(root)));
 | 
	
		
			
				|  |  | -            if (rootDir == s_dotSlash)
 | 
	
		
			
				|  |  | -            {
 | 
	
		
			
				|  |  | -                // Special case, otherwise we can return "./" instead of "" below!
 | 
	
		
			
				|  |  | -                return protoDir;
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | -            if (Platform.IsFsCaseInsensitive)
 | 
	
		
			
				|  |  | -            {
 | 
	
		
			
				|  |  | -                protoDir = protoDir.ToLowerInvariant();
 | 
	
		
			
				|  |  | -                rootDir = rootDir.ToLowerInvariant();
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | -            protoDir = EndWithSlash(protoDir);
 | 
	
		
			
				|  |  | -            if (!protoDir.StartsWith(rootDir))
 | 
	
		
			
				|  |  | -            {
 | 
	
		
			
				|  |  | -                Log.LogWarning("Protobuf item '{0}' has the ProtoRoot metadata '{1}' " +
 | 
	
		
			
				|  |  | -                  "which is not prefix to its path. Cannot compute relative path.",
 | 
	
		
			
				|  |  | -                  proto, root);
 | 
	
		
			
				|  |  | -                return "";
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | -            return protoDir.Substring(rootDir.Length);
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        // './' or '.\', normalized per system.
 | 
	
		
			
				|  |  | -        static string s_dotSlash = "." + Path.DirectorySeparatorChar;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        static string EndWithSlash(string str)
 | 
	
		
			
				|  |  | -        {
 | 
	
		
			
				|  |  | -            if (str == "")
 | 
	
		
			
				|  |  | -            {
 | 
	
		
			
				|  |  | -                return s_dotSlash;
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | -            else if (str[str.Length - 1] != '\\' && str[str.Length - 1] != '/')
 | 
	
		
			
				|  |  | -            {
 | 
	
		
			
				|  |  | -                return str + Path.DirectorySeparatorChar;
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | -            else
 | 
	
		
			
				|  |  | -            {
 | 
	
		
			
				|  |  | -                return str;
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -    };
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  |  }
 |