views:

4415

answers:

4

I'm trying to use protobuf in a C# project, using protobuf-net, and am wondering what is the best way to organise this into a Visual Studio project structure.

When manually using the protogen tool to generate code into C#, life seems easy but it doesn't feel right.

I'd like the .proto file to be considered to be the primary source-code file, generating C# files as a by-product, but before the C# compiler gets involved.

The options seem to be:

  1. Custom tool for proto tools (although I can't see where to start there)
  2. Pre-build step (calling protogen or a batch-file which does that)

I have struggled with 2) above as it keeps giving me "The system cannot find the file specified" unless I use absolute paths (and I don't like forcing projects to be explicitly located).

Is there a convention (yet) for this?


Edit: Based upon @jon's comments, I retried the pre-build step method and used this (protogen's location hardcoded for now), using Google's address-book example:

c:\bin\protobuf\protogen "-i:$(ProjectDir)AddressBook.proto" 
       "-o:$(ProjectDir)AddressBook.cs" -t:c:\bin\protobuf\csharp.xslt


Edit2: Taking @jon's recommendation to minimise build-time by not processing the .proto files if they haven't changed, I've knocked together a basic tool to check for me (this could probably be expanded to a full Custom-Build tool):

using System;
using System.Diagnostics;
using System.IO;

namespace PreBuildChecker
{
    public class Checker
    {
        static int Main(string[] args)
        {
            try
            {
                Check(args);
                return 0;
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
                return 1;
            }
        }

        public static void Check(string[] args)
        {
            if (args.Length < 3)
            {
                throw new ArgumentException(
                    "Command line must be supplied with source, target and command-line [plus options]");
            }

            string source = args[0];
            string target = args[1];
            string executable = args[2];
            string arguments = args.Length > 3 ? GetCommandLine(args) : null;

            FileInfo targetFileInfo = new FileInfo(target);
            FileInfo sourceFileInfo = new FileInfo(source);
            if (!sourceFileInfo.Exists) 
            {
                throw new ArgumentException(string.Format(
                    "Source file {0} not found", source));
            }

            if (!targetFileInfo.Exists || 
                sourceFileInfo.LastWriteTimeUtc > targetFileInfo.LastAccessTimeUtc)
            {
                Process process = new Process();
                process.StartInfo.FileName = executable;
                process.StartInfo.Arguments = arguments;
                process.StartInfo.ErrorDialog = true;

                Console.WriteLine(string.Format(
                     "Source newer than target, launching tool: {0} {1}",
                     executable,
                     arguments));
                process.Start();
            }
        }

        private static string GetCommandLine(string[] args)
        {
            string[] arguments = new string[args.Length - 3];
            Array.Copy(args, 3, arguments, 0, arguments.Length);
            return String.Join(" ", arguments);
        }
    }
}

My pre-build command is now (all on one line):

$(SolutionDir)PreBuildChecker\$(OutDir)PreBuildChecker 
    $(ProjectDir)AddressBook.proto 
    $(ProjectDir)AddressBook.cs 
    c:\bin\protobuf\protogen 
      "-i:$(ProjectDir)AddressBook.proto" 
      "-o:$(ProjectDir)AddressBook.cs" 
      -t:c:\bin\protobuf\csharp.xslt
+4  A: 

Calling a pre-build step but using project variables (e.g. $(ProjectPath)) to create absolute filenames without having them actually in your solution would seem a reasonable bet to me.

One thing you might want to consider, based on my past experience of code generators: you might want to write a wrapper for protogen which generates code to a different location, then checks whether the newly generated code is the same as the old code, and doesn't overwrite it if so. That way Visual Studio will realise nothing's changed and not force that project to be rebuilt - this has cut build times dramatically for me in the past.

Alternatively, you could keep an md5 hash of the .proto file the last time protogen was executed, and only execute protogen if the .proto file has changed - even less to do on each build!

Thanks for raising this as a question though - it clearly suggests I should work out a way to make this an easy pre-build step for my own port.

Jon Skeet
Thanks, I must have mistyped something before on the pre-build step. It would be nice if VisualStudio could do all of the "make" style checks regarding cs being older than proto files!
Ray Hayes
Yes, that would indeed be nice :) There may be a way of getting it to do that, but it would probably require more MSBuild-fu than I have, I'm afraid. The force is stronger with Marc in that respect, so he may well have a better answer.
Jon Skeet
@jon, I've now added a pre-build and a simple tool to check if the "written" date/time for the .proto is newer than the .cs, if so, it calls the protogen command.
Ray Hayes
oh, by added, I mean updated the original post with an "edit" block... not sure what the correct way to do this is....
Ray Hayes
lol! Caught me napping...
Marc Gravell
+2  A: 
Andrey Tch.
+2  A: 

I have attached a quick and dirty Visual Studio Custom Tool wrapper around ProtoGen.exe to the Google Code page for this issue (http://code.google.com/p/protobuf-net/issues/detail?id=39). This makes adding .proto files to C# projects extremely easy.

See the readme in the attachment for more info.

+4  A: 
Marc Gravell