Take the 2-minute tour ×
Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It's 100% free, no registration required.

I just wrote this short little program to increment up the build number for my projects every time I build them.

After compiling this exe, I just call it in the pre-build command line.

I did take out the filename string because I used it thrice, and I figured I'd get some flack if I didn't.

static void Main()
{
    int num;
    string file = "AssemblyInfo.cs";
    if (File.Exists(file))
        File.WriteAllLines(file, File.ReadAllLines(file).Select(s => !s.Trim().StartsWith("[assembly: AssemblyFileVersion(") ? s : string.Join(".", s.Trim(']').Trim(')').Trim('"').Split('.').Select((n, i) => i != 3 ? n : !int.TryParse(n, out num) ? n : (num + 1).ToString())) + "\")]"));
}

I realize this is complex, and the average person wont be able to read it. I use magic numbers, and I do some things specifically to keep the glory on 1 line. Feel free to bask in the presence of psychedelic code (which just wants to be your friend).

share|improve this question
    
How about just having [assembly: AssemblyVersion("1.0.*")] in your AssemblyInfo.cs file? –  Mat's Mug Apr 22 at 17:16
    
@Mat'sMug Last I checked(years ago), that didn't work as I'd liked. The numbers weren't incrementing, but instead being eradic and random, I probably should have checked again before writing this :D –  BenVlodgi Apr 22 at 17:22
    
You're right, the build number does look somewhat random, but a new build will always have a higher build number than the previous. What need is there to have it exactly incremental? –  Mat's Mug Apr 22 at 17:24
2  
Is it possible that the revision number went up when the build number went down? –  Mat's Mug Apr 22 at 18:04
1  
I suggest using the System.Version class (Version.Parse, then create an incremented object, then call Version.ToString) to reduce the string gymnastics. –  Ani Apr 23 at 3:30
show 3 more comments

6 Answers 6

up vote 13 down vote accepted

Why not use Regex.Match instead of using those confusing joins/splits? Unless you are purposely trying to code golf this, doing it using regular expression is much easier to follow logically.

After taking into account delegates used with Regex.Replace (as explained in svick's comment), here is a functionally superior solution to the previous answer I posted. This eliminates much of the unnecessary lambda pieces. The original solution is arguably not a good use of LINQ because of the changes being made to the collection (ie. not a very strong use of queries, despite lambdas having that functionality).

static void Main()
{
    string file = "AssemblyInfo.cs";
    if (File.Exists(file))
        File.WriteAllText(file,
            Regex.Replace(
                File.ReadAllText(file),
                @"(?<=\[assembly: AssemblyFileVersion\(""[0-9]*.[0-9]*.)[0-9]*(?=.[0-9]*""\)\])",
                m => (Convert.ToInt16(m.Value) + 1).ToString()
            )
        );
    }
}

Still only one semi-colon, but at least it's possible to look at it and recognize each piece for what it does. Note I split the parentheses onto separate lines here for parameter readability, but some programmers disagree with this style of line-breaking.

It basically works similar to your original solution, but it basically finds and replaces the Regex.Match with an incremented value.

share|improve this answer
    
Wouldn't this be even better by using Regex.Replace? That way, you wouldn't have to do anything with parts that don't change. –  svick Apr 22 at 20:10
    
@svick: I'm not sure the Regex engine is able to perform number increments like that within Regex.Replace. Maybe you can accomplish this by using a delegate. –  grovesNL Apr 22 at 20:19
    
@CodesInChaos: foreach uses an enumerator. How would you modify the value of the line in this way? –  grovesNL Apr 22 at 21:33
    
Yeah, delegate, along with look-ahead and look-behind: string expr = @"(?<=\[assembly: AssemblyFileVersion\(""[0-9]*.[0-9]*.)[0-9]*(?=.[0-9]*""\)\])"; file[i] = Regex.Replace(file[i], expr, m => (Convert.ToInt16(m.Value) + 1).ToString());. –  svick Apr 22 at 22:30
    
@svick: Nice, I like that. Feel free to edit it directly into my post if you'd like (I don't have access to a compiler right now to test). –  grovesNL Apr 23 at 1:04
add comment

I think it's not completely terrible to cram a lot of stuff in a single chain of instructions. But, for the love of god, you have to break that line. Perhaps something like this:

if (File.Exists(file)) {
    File.WriteAllLines(file,
            File.ReadAllLines(file)
                    .Select(s => !s.Trim().StartsWith("[assembly: AssemblyFileVersion(")
                    ? s
                    : string.Join(".", s.Trim(']').Trim(')').Trim('"').Split('.')
                    .Select((n, i) => i != 3
                    ? n
                    : !int.TryParse(n, out num)
                    ? n
                    : (num + 1).ToString())) + "\")]"));
}

I don't have a Visual Studio with me. I used the auto-format function of IntelliJ to get this one (sort of). I'm sure Visual Studio too has something to reformat this nicely. Use it!


In addition to the above, to put it simply, you want to change this:

[assembly: AssemblyVersion("x.y.z")]

to this:

[assembly: AssemblyVersion("x.y.z+1")]

In addition to expanding the long chain to multiple lines, I would point out that this part is ugly:

string.Join(".", s.Trim(']').Trim(')').Trim('"').Split('.')

It would be better to use an approach like this, sorry for the Perl implementation:

sub increment_version {
    my ($before, $minor, $after) = @_;
    $before . ($minor + 1) . $after;
}
s/(\[assembly: AssemblyFileVersion\("\d+\.\d+\.)(\d+)("\)\])/&increment_version($1, $2, $3)/e;

The idea here is to use a regular expression to extract the part you want to change, the part before and after it, and replace the whole thing with the middle part changed as you wish.

Finally, but perhaps most importantly, you definitely want to unit test the middle part that replaces the text. When you do that, you will have to extract the entire first Select into its own method, which will do you a lot of good, and make your implementation more readable.

share|improve this answer
add comment

There is way too much code in that last line. I'm not a stickler for wrapping at 80 character, but 280 characters seems like a bit much. Just because this is a build step script doesn't mean you code doesn't have to be readable. Write a real function to do this operation so I don't have to trust that you are writing the line back the correct way.

share|improve this answer
add comment

Don't try to be a badass cowboy. ☺

Stuffing everything onto one line of code is trying to be badass. You could improve clarity simply by adding whitespace.

Producing an incremented version number by careless string processing is trying to be a cowboy. I suggest introducing some kind of abstraction, such as this SemVer class. If you don't want to introduce an external dependency, it wouldn't be that hard to incorporate just the routines you need into your own code.

A regular expression substitution with a callback would be the way to go, I think.

static string IncrVersion(Match m)
{
    SemVersion version = SemVersion.Parse(match.Groups[1].Value);
    return version.Change(patch: 1 + version.Patch).ToString();
}

static void Main()
{
    string file = "AssemblyInfo.cs";
    var versionRx = new Regex(@"\[assembly: AssemblyFileVersion\((.*)\)\]");
    if (File.Exists(file)) 
    {
        File.WriteAllLines(file,
            File.ReadAllLines(file).Select(s =>
                versionRx.Replace(s, new MatchEvaluator(IncrVersion))
            )   
        );  
    }
}   
share|improve this answer
    
I'm no C# programmer. Please feel free to correct the code above. –  200_success Apr 22 at 23:19
add comment

Well, you could also use a custom build target that increments the build number automatically.

Using MSBuild.Community.Tasks, this is pretty easy:

<Version VersionFile="Version.txt" BuildType="Incremental">
  <Output TaskParameter="Major" PropertyName="Major" />
  <Output TaskParameter="Minor" PropertyName="Minor" />
  <Output TaskParameter="Build" PropertyName="Build" />
  <Output TaskParameter="Revision" PropertyName="Revision" />
</Version>
<AssemblyInfo 
   AssemblyVersion="$(Major).$(Minor).$(Build).$(Revision)"
   AssemblyFileVersion="$(Major).$(Minor).$(Build).$(Revision)" 
   CodeLanguage="CS" ComVisible="False" />
share|improve this answer
1  
While it doesn't review the code in the OP itself, this certainly strikes towards the OP's goal, using a different part of the same technology. +1 –  Brian S Apr 23 at 20:17
add comment
i != 3 ? n : !int.TryParse(n, out num) ? n : code

Could be

i != 3 || !int.TryParse(n, out num) ? n : code

...saves 3 characters, and makes more logical sense.

Also, instead of checking that index, it could be re-written to store the result, then only modify the 3rd indexed location and TryParse that.

share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.