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

Added methods to cover manipulation of Content Nodes in project files #1335

Merged
merged 4 commits into from
Aug 1, 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
126 changes: 122 additions & 4 deletions src/app/FakeLib/MSBuild/ProjectSystem.fs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ type ProjectFile(projectFileName:string,documentContent : string) =
let getCompileNodes (document:XmlDocument) =
[for node in document.SelectNodes(compileNodesXPath,nsmgr) -> node]

let contentNodesXPath = "/default:Project/default:ItemGroup/default:Content"
let getContentNodes (document:XmlDocument) =
[for node in document.SelectNodes(contentNodesXPath,nsmgr) -> node]

let getFileAttribute (node:XmlNode) = node.Attributes.["Include"].InnerText

/// Read a Project from a FileName
Expand All @@ -38,6 +42,16 @@ type ProjectFile(projectFileName:string,documentContent : string) =

node.ParentNode.AppendChild(newNode) |> ignore
new ProjectFile(projectFileName,document.OuterXml)

/// Add a file to the Content nodes
member x.AddContentFile fileName =
let document = XMLDoc documentContent // we create a copy and work immutable
let node = getContentNodes document |> Seq.last
let newNode = node.CloneNode(false) :?> XmlElement
newNode.SetAttribute("Include",fileName)

node.ParentNode.AppendChild(newNode) |> ignore
new ProjectFile(projectFileName,document.OuterXml)

/// Removes a file from the Compile nodes
member x.RemoveFile fileName =
Expand All @@ -51,8 +65,23 @@ type ProjectFile(projectFileName:string,documentContent : string) =

new ProjectFile(projectFileName,document.OuterXml)

/// Removes a file from the Content nodes
member x.RemoveContentFile fileName =
let document = XMLDoc documentContent // we create a copy and work immutable
let node =
getContentNodes document
|> List.filter (fun node -> getFileAttribute node = fileName)
|> Seq.last // we remove the last one to make easier to remove duplicates

node.ParentNode.RemoveChild node |> ignore

new ProjectFile(projectFileName,document.OuterXml)

/// All files which are in "Compile" sections
member x.Files = getCompileNodes document |> List.map getFileAttribute

/// All files which are in "Content" sections
member x.ContentFiles = getContentNodes document |> List.map getFileAttribute

/// Finds duplicate files which are in "Compile" sections
member this.FindDuplicateFiles() =
Expand All @@ -64,9 +93,24 @@ type ProjectFile(projectFileName:string,documentContent : string) =
| true,true -> () // already seen at least twice
]

member x.RemoveDuplicates() =
x.FindDuplicateFiles()
|> List.fold (fun (project:ProjectFile) duplicate -> project.RemoveFile duplicate) x
member x.RemoveDuplicates() =
x.FindDuplicateFiles()
|> List.fold (fun (project:ProjectFile) duplicate -> project.RemoveFile duplicate) x


/// Finds duplicate files which are in "Content" sections
member this.FindDuplicateContentFiles() =
[let dict = Dictionary()
for file in this.ContentFiles do
match dict.TryGetValue file with
| false,_ -> dict.[file] <- false // first observance
| true,false -> dict.[file] <- true; yield file // second observance
| true,true -> () // already seen at least twice
]

member x.RemoveDuplicatesContent() =
x.FindDuplicateContentFiles()
|> List.fold (fun (project:ProjectFile) duplicate -> project.RemoveContentFile duplicate) x

/// The project file name
member x.ProjectFileName = projectFileName
Expand Down Expand Up @@ -115,6 +159,37 @@ let findMissingFiles templateProject projects =
UnorderedFiles = unorderedFiles })
|> Seq.filter (fun pc -> pc.HasErrors)

/// Compares the given project files againts the template project and returns which files are missing.
/// For F# projects it is also reporting unordered files.
let findMissingContentFiles templateProject projects =
let isFSharpProject file = file |> endsWith ".fsproj"

let templateFiles = (ProjectFile.FromFile templateProject).ContentFiles
let templateFilesSet = Set.ofSeq templateFiles

projects
|> Seq.map (fun fileName -> ProjectFile.FromFile fileName)
|> Seq.map (fun ps ->
let missingFiles = Set.difference templateFilesSet (Set.ofSeq ps.ContentFiles)

let unorderedFiles =
if not <| isFSharpProject templateProject then [] else
if not <| Seq.isEmpty missingFiles then [] else
let remainingFiles = ps.Files |> List.filter (fun file -> Set.contains file templateFilesSet)
if remainingFiles.Length <> templateFiles.Length then [] else

templateFiles
|> List.zip remainingFiles
|> List.filter (fun (a,b) -> a <> b)
|> List.map fst

{ TemplateProjectFileName = templateProject
ProjectFileName = ps.ProjectFileName
MissingFiles = missingFiles
DuplicateFiles = ps.FindDuplicateContentFiles()
UnorderedFiles = unorderedFiles })
|> Seq.filter (fun pc -> pc.HasErrors)

/// Compares the given projects to the template project and adds all missing files to the projects if needed.
let FixMissingFiles templateProject projects =
let addMissing (project:ProjectFile) missingFile =
Expand All @@ -128,6 +203,19 @@ let FixMissingFiles templateProject projects =
let newProject = Seq.fold addMissing project pc.MissingFiles
newProject.Save())

/// Compares the given projects to the template project and adds all missing files to the projects if needed.
let FixMissingContentFiles templateProject projects =
let addMissing (project:ProjectFile) missingFile =
tracefn "Adding %s to %s" missingFile project.ProjectFileName
project.AddContentFile missingFile

findMissingContentFiles templateProject projects
|> Seq.iter (fun pc ->
let project = ProjectFile.FromFile pc.ProjectFileName
if not (Seq.isEmpty pc.MissingFiles) then
let newProject = Seq.fold addMissing project pc.MissingFiles
newProject.Save())

/// It removes duplicate files from the project files.
let RemoveDuplicateFiles projects =
projects
Expand All @@ -137,12 +225,28 @@ let RemoveDuplicateFiles projects =
let newProject = project.RemoveDuplicates()
newProject.Save())

/// It removes duplicate content files from the project files.
let RemoveDuplicateContentFiles projects =
projects
|> Seq.iter (fun fileName ->
let project = ProjectFile.FromFile fileName
if not (project.FindDuplicateContentFiles().IsEmpty) then
let newProject = project.RemoveDuplicatesContent()
newProject.Save())

/// Compares the given projects to the template project and adds all missing files to the projects if needed.
/// It also removes duplicate files from the project files.
let FixProjectFiles templateProject projects =
FixMissingFiles templateProject projects
RemoveDuplicateFiles projects


/// Compares the given projects to the template project and adds all missing content files to the projects if needed.
/// It also removes duplicate files from the project files.
let FixProjectContentFiles templateProject projects =
FixMissingContentFiles templateProject projects
RemoveDuplicateContentFiles projects

/// Compares the given project files againts the template project and fails if any files are missing.
/// For F# projects it is also reporting unordered files.
let CompareProjectsTo templateProject projects =
Expand Down Expand Up @@ -172,12 +276,26 @@ let removeCompileNodesWithMissingFiles includeExistsF (project:ProjectFile) =
if not (includeExistsF(includePath)) then yield filePath }
missingFiles
|> Seq.fold (fun (project:ProjectFile) file -> project.RemoveFile(file)) project

let removeContentNodesWithMissingFiles includeExistsF (project:ProjectFile) =
let projectDir = IO.Path.GetDirectoryName(project.ProjectFileName)
let missingFiles =
seq { for filePath in project.ContentFiles do
// We have to normalize the path, because csproj can have win style directory separator char on Mono too
// Xbuild handles them, so we do too http://www.mono-project.com/archived/porting_msbuild_projects_to_xbuild/#paths
let includePath = Globbing.normalizePath (IO.Path.Combine([|projectDir; filePath|]))
if not (includeExistsF(includePath)) then yield filePath }
missingFiles
|> Seq.fold (fun (project:ProjectFile) file -> project.RemoveContentFile(file)) project

/// Removes projects Compile nodes that have Include attributes pointing to files missing from the file system. Saves updated projects.
let RemoveCompileNodesWithMissingFiles project =
let newProject = removeCompileNodesWithMissingFiles System.IO.File.Exists (ProjectFile.FromFile project)
newProject.Save()


/// Removes projects Content nodes that have Include attributes pointing to files missing from the file system. Saves updated projects.
let RemoveContentNodesWithMissingFiles project =
let newProject = removeContentNodesWithMissingFiles System.IO.File.Exists (ProjectFile.FromFile project)
newProject.Save()


4 changes: 2 additions & 2 deletions src/app/FakeLib/MSBuild/SpecsRemovement.fs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ let AllSpecFiles elementName (s:string) = s.EndsWith "Specs.cs" || s.EndsWith "S
let AllSpecAndTestDataFiles elementName (s:string) =
AllSpecFiles elementName s || ((elementName = "Content" || elementName = "None") && s.Contains("TestData"))

/// A Convetion which matches nothing
/// A Convention which matches nothing
let Nothing _ _ = false

/// Removes all NUnit references from a project.
Expand All @@ -83,4 +83,4 @@ let RemoveAllNUnitReferences projectFileName =

/// Removes all spec and test data references from a project.
let RemoveAllSpecAndTestDataFiles projectFileName =
RemoveTestsFromProject Nothing AllSpecAndTestDataFiles projectFileName
RemoveTestsFromProject Nothing AllSpecAndTestDataFiles projectFileName