Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AssemblyInfoFile: add functions to read and update attributes #1073

Merged
merged 2 commits into from
Jan 14, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 112 additions & 0 deletions src/app/FakeLib/AssemblyInfoFile.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,39 @@
/// There is also a tutorial about the [AssemblyInfo tasks](../assemblyinfo.html) available.
module Fake.AssemblyInfoFile

open System
open System.IO
open System.Text.RegularExpressions

let internal assemblyVersionRegex = getRegEx @"([0-9]+.)+[0-9]+"

// matches [assembly: name(value)] and captures "name" and "value" as named captures. Variations for C#, F#, C++ and VB
let private regexAttrNameValueCs = @"^\s*\[\s*assembly:\s*(?<name>\w+?)\s*\((?<value>.*)\)\s*\]\s*$"
let private regexAttrNameValueFs = @"^\s*\[\<\s*assembly:\s*(?<name>\w+?)\s*\((?<value>.*)\)\s*\>\]\s*$"
let private regexAttrNameValueCpp = @"^\s*\[\s*assembly:\s*(?<name>\w+?)\s*\((?<value>.*)\)\s*\]\s*;\s*$"
let private regexAttrNameValueVb = @"^\s*\<\s*assembly:\s*(?<name>\w+?)\s*\((?<value>.*)\)\s*\>\s*$"

// matches [assembly: name(value)] but only captures the value. Variations for C#, F#, C++ and VB
let private regexAttrValueCs name =
@"(?<=^\s*\[\s*assembly:\s*" + name + @"(?:Attribute)?\s*\()" // look-behind for "[assembly: name[Attribute]("
+ @"(.*)" // value
+ @"(?=\)\s*\]\s*$)" // look-ahead for ")]"

let private regexAttrValueFs name =
@"(?<=^\s*\[\<\s*assembly:\s*" + name + @"(?:Attribute)?\s*\()" // look-behind for "[<assembly: name[Attribute]("
+ @"(.*)" // value
+ @"(?=\)\s*\>\]\s*$)" // look-ahead for ")>]"

let private regexAttrValueCpp name =
@"(?<=^\s*\[\s*assembly:\s*" + name + @"(?:Attribute)?\s*\()" // look-behind for "[assembly: name[Attribute]("
+ @"(.*)" // value
+ @"(?=\)\s*\]\s*;\s*$)" // look-ahead for ")];"

let private regexAttrValueVb name =
@"(?<=^\s*\<\s*assembly:\s*" + name + @"(?:Attribute)?\s*\()" // look-behind for "<assembly: name[Attribute]("
+ @"(.*)" // value
+ @"(?=\)\s*\>\s*$)" // look-ahead for ")>"

let private NormalizeVersion version =
let m = assemblyVersionRegex.Match(version)
if m.Captures.Count > 0 then m.Captures.[0].Value
Expand Down Expand Up @@ -231,3 +262,84 @@ let CreateVisualBasicAssemblyInfo outputFileName attributes =
/// Creates a C++/CLI AssemblyInfo file with the given attributes.
let CreateCppCliAssemblyInfo outputFileName attributes =
CreateCppCliAssemblyInfoWithConfig outputFileName attributes AssemblyInfoFileConfig.Default

let private removeAtEnd (textToRemove:string) (text:string) =
if text.EndsWith(textToRemove) then
text.Substring(0, text.Length - textToRemove.Length)
else
text

/// Read attributes from an AssemblyInfo file and return as a sequence of Attribute.
/// ## Parameters
/// - `assemblyInfoFile` - The file to read attributes from. Language C#, F#, VB or C++ is determined from the extension.
let GetAttributes assemblyInfoFile =
let text = File.ReadAllText assemblyInfoFile

let regex =
if assemblyInfoFile.ToLower().EndsWith(".cs") then regexAttrNameValueCs
elif assemblyInfoFile.ToLower().EndsWith(".fs") then regexAttrNameValueFs
elif assemblyInfoFile.ToLower().EndsWith(".vb") then regexAttrNameValueVb
elif assemblyInfoFile.ToLower().EndsWith(".cpp") then regexAttrNameValueCpp
else
failwithf "Assembly info file type not supported: %s" assemblyInfoFile

Regex.Matches(text, regex, RegexOptions.Multiline)
|> Seq.cast<Match>
|> Seq.map (fun m -> Attribute(m.Groups.["name"].Value |> removeAtEnd "Attribute",
m.Groups.["value"].Value,
""))

/// Read a single attribute from an AssemblyInfo file.
/// ## Parameters
/// - `attrName` - Name of the attribute without "Attribute" at the end.
/// - `assemblyInfoFile` - The file to read from. Language C#, F#, VB or C++ is determined from the extension.
let GetAttribute attrName assemblyInfoFile =
assemblyInfoFile |> GetAttributes |> Seq.tryFind (fun a -> a.Name = attrName)

/// Read the value of a single attribute from an AssemblyInfo file. Note that string values are returned with surrounding "".
/// ## Parameters
/// - `attrName` - Name of the attribute without "Attribute" at the end.
/// - `assemblyInfoFile` - The file to read from. Language C#, F#, VB or C++ is determined from the extension.
let GetAttributeValue attrName assemblyInfoFile =
match GetAttribute attrName assemblyInfoFile with
| Some attr -> Some attr.Value
| None -> None

let private updateAttr regexFactory text (attribute:Attribute) =
let regex = regexFactory attribute.Name

let m = Regex.Match(text, regex, RegexOptions.Multiline)

// Replace if found with different value
if m.Success && m.Value <> attribute.Value then
tracefn "Attribute '%s' updated: %s" attribute.Name attribute.Value
Regex.Replace(text, regex, attribute.Value, RegexOptions.Multiline)

// Do nothing if found with the same value
elif m.Success then
tracefn "Attribute '%s' is already correct: %s" attribute.Name attribute.Value
text

// Fail if not found
else
failwithf "Attribute '%s' not found" attribute.Name

/// Update a set of attributes in an AssemblyInfo file. Fails if any attribute is not found.
/// ## Parameters
/// - `assemblyInfoFile` - The file to update. Language C#, F#, VB or C++ is determined from the extension.
/// - `attributes` - The Attributes that should be updated matched on Name (Namespace is not used).
let UpdateAttributes assemblyInfoFile (attributes: seq<Attribute>) =
tracefn "Updating attributes in: %s" assemblyInfoFile

let regexFactory =
if assemblyInfoFile.ToLower().EndsWith(".cs") then regexAttrValueCs
elif assemblyInfoFile.ToLower().EndsWith(".fs") then regexAttrValueFs
elif assemblyInfoFile.ToLower().EndsWith(".vb") then regexAttrValueVb
elif assemblyInfoFile.ToLower().EndsWith(".cpp") then regexAttrValueCpp
else
failwithf "Assembly info file type not supported: %s" assemblyInfoFile

let text = File.ReadAllText assemblyInfoFile
let newText = attributes |> Seq.fold (updateAttr regexFactory) text

File.WriteAllText(assemblyInfoFile, newText)
195 changes: 195 additions & 0 deletions src/test/Test.FAKECore/AssemblyInfoSpecs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,50 @@ public class when_using_fsharp_task_with_default_config
File.ReadAllText(infoFile)
.ShouldEqual(expected.Replace("\r\n", Environment.NewLine));
};

It update_attributes_should_update_attributes_in_fs_file = () =>
{
// Arrange. Create attribute both with and without "Attribute" at the end
string infoFile = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".fs");
const string original = "namespace System\r\nopen System.Reflection\r\n\r\n" +
"[<assembly: AssemblyProduct(\"TestLib\")>]\r\n" +
"[<assembly: AssemblyVersionAttribute(\"1.0.0.0\")>]\r\n";
File.WriteAllText(infoFile, original.Replace("\r\n", Environment.NewLine));

// Act
var attributes = new[]
{
AssemblyInfoFile.Attribute.Product("TestLibNew"),
AssemblyInfoFile.Attribute.Version("2.0.0.0")
};
AssemblyInfoFile.UpdateAttributes(infoFile, attributes);

// Assert
const string expected = "namespace System\r\nopen System.Reflection\r\n\r\n" +
"[<assembly: AssemblyProduct(\"TestLibNew\")>]\r\n" +
"[<assembly: AssemblyVersionAttribute(\"2.0.0.0\")>]\r\n";

File.ReadAllText(infoFile)
.ShouldEqual(expected.Replace("\r\n", Environment.NewLine));
};

It get_attribute_should_read_attribute_from_fs_file = () =>
{
// Arrange. Create attribute both with and without "Attribute" at the end
string infoFile = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".fs");
const string original = "namespace System\r\nopen System.Reflection\r\n\r\n" +
"[<assembly: AssemblyProduct(\"TestLib\")>]\r\n" +
"[<assembly: AssemblyVersionAttribute(\"1.0.0.0\")>]\r\n";
File.WriteAllText(infoFile, original.Replace("\r\n", Environment.NewLine));

// Act
var productAttr = AssemblyInfoFile.GetAttribute("AssemblyProduct", infoFile).Value;
var versionAttr = AssemblyInfoFile.GetAttribute("AssemblyVersion", infoFile).Value;

// Assert
productAttr.Value.ShouldEqual("\"TestLib\"");
versionAttr.Value.ShouldEqual("\"1.0.0.0\"");
};
}

public class when_using_fsharp_task_with_custom_config
Expand Down Expand Up @@ -65,6 +109,50 @@ public class when_using_csharp_task_with_default_config
File.ReadAllText(infoFile)
.ShouldEqual(expected.Replace("\r\n", Environment.NewLine));
};

It update_attributes_should_update_attributes_in_cs_file = () =>
{
// Arrange. Create attribute both with and without "Attribute" at the end
string infoFile = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".cs");
const string original = "// <auto-generated/>\r\nusing System.Reflection;\r\n\r\n" +
"[assembly: AssemblyProduct(\"TestLib\")]\r\n" +
"[assembly: AssemblyVersionAttribute(\"1.0.0.0\")]\r\n";
File.WriteAllText(infoFile, original.Replace("\r\n", Environment.NewLine));

// Act
var attributes = new[]
{
AssemblyInfoFile.Attribute.Product("TestLibNew"),
AssemblyInfoFile.Attribute.Version("2.0.0.0")
};
AssemblyInfoFile.UpdateAttributes(infoFile, attributes);

// Assert
const string expected = "// <auto-generated/>\r\nusing System.Reflection;\r\n\r\n" +
"[assembly: AssemblyProduct(\"TestLibNew\")]\r\n" +
"[assembly: AssemblyVersionAttribute(\"2.0.0.0\")]\r\n";

File.ReadAllText(infoFile)
.ShouldEqual(expected.Replace("\r\n", Environment.NewLine));
};

It get_attribute_should_read_attribute_from_cs_file = () =>
{
// Arrange. Create attribute both with and without "Attribute" at the end
string infoFile = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".cs");
const string original = "// <auto-generated/>\r\nusing System.Reflection;\r\n\r\n" +
"[assembly: AssemblyProduct(\"TestLib\")]\r\n" +
"[assembly: AssemblyVersionAttribute(\"1.0.0.0\")]\r\n";
File.WriteAllText(infoFile, original.Replace("\r\n", Environment.NewLine));

// Act
var productAttr = AssemblyInfoFile.GetAttribute("AssemblyProduct", infoFile).Value;
var versionAttr = AssemblyInfoFile.GetAttribute("AssemblyVersion", infoFile).Value;

// Assert
productAttr.Value.ShouldEqual("\"TestLib\"");
versionAttr.Value.ShouldEqual("\"1.0.0.0\"");
};
}


Expand All @@ -84,5 +172,112 @@ public class when_using_cppcli_task_with_default_config
File.ReadAllText(infoFile)
.ShouldEqual(expected.Replace("\r\n", Environment.NewLine));
};

It update_attributes_should_update_attributes_in_cpp_file = () =>
{
// Arrange. Create attribute both with and without "Attribute" at the end
string infoFile = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".cpp");
const string original = "// <auto-generated/>\r\nusing namespace System::Reflection;\r\n\r\n" +
"[assembly:AssemblyProduct(\"TestLib\")];\r\n" +
"[assembly:AssemblyVersionAttribute(\"1.0.0.0\")];\r\n";
File.WriteAllText(infoFile, original.Replace("\r\n", Environment.NewLine));

// Act
var attributes = new[]
{
AssemblyInfoFile.Attribute.Product("TestLibNew"),
AssemblyInfoFile.Attribute.Version("2.0.0.0")
};
AssemblyInfoFile.UpdateAttributes(infoFile, attributes);

// Assert
const string expected = "// <auto-generated/>\r\nusing namespace System::Reflection;\r\n\r\n" +
"[assembly:AssemblyProduct(\"TestLibNew\")];\r\n" +
"[assembly:AssemblyVersionAttribute(\"2.0.0.0\")];\r\n";

File.ReadAllText(infoFile)
.ShouldEqual(expected.Replace("\r\n", Environment.NewLine));
};

It get_attribute_should_read_attribute_from_cpp_file = () =>
{
// Arrange. Create attribute both with and without "Attribute" at the end
string infoFile = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".cpp");
const string original = "// <auto-generated/>\r\nusing namespace System::Reflection;\r\n\r\n" +
"[assembly:AssemblyProduct(\"TestLib\")];\r\n" +
"[assembly:AssemblyVersionAttribute(\"1.0.0.0\")];\r\n";
File.WriteAllText(infoFile, original.Replace("\r\n", Environment.NewLine));

// Act
var productAttr = AssemblyInfoFile.GetAttribute("AssemblyProduct", infoFile).Value;
var versionAttr = AssemblyInfoFile.GetAttribute("AssemblyVersion", infoFile).Value;

// Assert
productAttr.Value.ShouldEqual("\"TestLib\"");
versionAttr.Value.ShouldEqual("\"1.0.0.0\"");
};
}


public class when_using_vb_task_with_default_config
{
It should_emit_valid_syntax = () =>
{
string infoFile = Path.GetTempFileName();
var attributes = new[]
{
AssemblyInfoFile.Attribute.Product("TestLib"),
AssemblyInfoFile.Attribute.Version("1.0.0.0")
};
AssemblyInfoFile.CreateVisualBasicAssemblyInfo(infoFile, attributes);
const string expected = "' <auto-generated/>\r\nImports System.Reflection\r\n\r\n<assembly: AssemblyProductAttribute(\"TestLib\")>\r\n<assembly: AssemblyVersionAttribute(\"1.0.0.0\")>\r\nFriend NotInheritable Class AssemblyVersionInformation\r\n Friend Const Version As String = \"1.0.0.0\"\r\nEnd Class\r\n";

File.ReadAllText(infoFile)
.ShouldEqual(expected.Replace("\r\n", Environment.NewLine));
};

It update_attributes_should_update_attributes_in_vb_file = () =>
{
// Arrange. Create attribute both with and without "Attribute" at the end
string infoFile = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".vb");
const string original = "' <auto-generated/>\r\nImports System.Reflection\r\n\r\n" +
"<assembly: AssemblyProduct(\"TestLib\")>\r\n" +
"<assembly: AssemblyVersionAttribute(\"1.0.0.0\")>\r\n";
File.WriteAllText(infoFile, original.Replace("\r\n", Environment.NewLine));

// Act
var attributes = new[]
{
AssemblyInfoFile.Attribute.Product("TestLibNew"),
AssemblyInfoFile.Attribute.Version("2.0.0.0")
};
AssemblyInfoFile.UpdateAttributes(infoFile, attributes);

// Assert
const string expected = "' <auto-generated/>\r\nImports System.Reflection\r\n\r\n" +
"<assembly: AssemblyProduct(\"TestLibNew\")>\r\n" +
"<assembly: AssemblyVersionAttribute(\"2.0.0.0\")>\r\n";

File.ReadAllText(infoFile)
.ShouldEqual(expected.Replace("\r\n", Environment.NewLine));
};

It get_attribute_should_read_attribute_from_vb_file = () =>
{
// Arrange. Create attribute both with and without "Attribute" at the end
string infoFile = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".vb");
const string original = "' <auto-generated/>\r\nImports System.Reflection\r\n\r\n" +
"<assembly: AssemblyProduct(\"TestLib\")>\r\n" +
"<assembly: AssemblyVersionAttribute(\"1.0.0.0\")>\r\n";
File.WriteAllText(infoFile, original.Replace("\r\n", Environment.NewLine));

// Act
var productAttr = AssemblyInfoFile.GetAttribute("AssemblyProduct", infoFile).Value;
var versionAttr = AssemblyInfoFile.GetAttribute("AssemblyVersion", infoFile).Value;

// Assert
productAttr.Value.ShouldEqual("\"TestLib\"");
versionAttr.Value.ShouldEqual("\"1.0.0.0\"");
};
}
}