Skip to content

Commit

Permalink
Merge pull request #1335 from AntoineGa/master
Browse files Browse the repository at this point in the history
Added methods to cover manipulation of Content Nodes in project files
  • Loading branch information
forki authored Aug 1, 2016
2 parents da404eb + 475c9a4 commit 79ee959
Show file tree
Hide file tree
Showing 2 changed files with 124 additions and 6 deletions.
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

0 comments on commit 79ee959

Please sign in to comment.