In the last months, I have been busy in a less-than-optimally organized C++ project.
This project involved work on a larger codebase that had absolutely no version control system available. All files were lying around in a semi-sorted directory structure on some intranet server. This, of course, made daily synching to this structure quite tedious. In particular, Visual Studio 2003 refuses to import recursive directories, and aborts importing if any files are already present within the solution.
It essentially discards all folders and files in selected projects, and rebuilds these from the solution directories.
During this process, new solution folders are created for each on-disk sub directory, so the source files remain nicely sorted in the IDE.
The macro has been tested in Visual Studio 2003, but should hopefully work in other VS versions with slight adaptions.
references need to be added to the Visual Studio macro project for the macro to work.
If anybody is interested in more integrated Visual Studio code, I also have a working COM-based C++ version of the macro available.
Code:
'==========================================================================
' Project generator main module.
'
' Copyright Wernaeh, Juli 2009
' Free for any use.
'
' Contact: wernaeh@gmail.com
'==========================================================================
Option Explicit On
Option Strict Off
Imports EnvDTE
Imports System.Diagnostics
Imports Microsoft.VisualStudio.VCProject
Imports Microsoft.VisualStudio.VCProjectEngine
Public Module ProjectGen
'==========================================================================
' Main project generation entry point
'==========================================================================
Sub regenerateMain()
'======================================================================
' Temporaries
'======================================================================
' Temporary loop variables
Dim i As Integer
Dim j As Integer
' Array with file extensions to be added
Dim exts(8) As String
exts(0) = ".c"
exts(1) = ".cpp"
exts(2) = ".h"
exts(3) = ".def"
exts(4) = ".ico"
exts(5) = ".rc"
exts(6) = ".flex"
exts(7) = ".bat"
'======================================================================
' Determine project selection
'======================================================================
' Retrieve an array with selected projects
Dim selectedprojects() As Project
Dim selectedprojectcount As Integer
selectedprojectcount = 0
For i = 1 To DTE.SelectedItems.Count
If TypeOf DTE.SelectedItems.Item(i).Project Is Project Then
selectedprojectcount += 1
Redim Preserve selectedprojects(selectedprojectcount - 1)
selectedprojects(selectedprojectcount - 1) = _
DTE.SelectedItems.Item(i).Project
End If
Next
' Notify the user if no projects have been selected
If selectedprojectcount = 0 Then
MsgBox("Please select some projects for processing with the" & _
vbNewLine & _
"project generator within the solution view.", _
MsgBoxStyle.OKOnly, _
"Please select projects!")
Return
End If
'======================================================================
' Handle open documents
'======================================================================
' Prompt the user whether to save all open documents, if there
' are any.
If DTE.Documents.Count > 0 Then
Dim usersaveall As MsgBoxResult
usersaveall = _
MsgBox("The project generator will close any open files." & _
vbNewLine & _
"Should these be saved now?", _
MsgBoxStyle.YesNoCancel, _
"Save open files now?")
If usersaveall = MsgBoxResult.Cancel
Return
End If
If usersaveall = MsgBoxResult.Yes
DTE.Documents.SaveAll()
End If
DTE.Documents.CloseAll(vsSaveChanges.vsSaveChangesNo)
End If
'======================================================================
' Regenerate projects
'======================================================================
' Now, regenerate each project
For i = 0 To selectedprojectcount - 1
Dim project As Project
project = selectedprojects(i)
'==================================================================
' Regeneration prompt
'==================================================================
' First, prompt whether regeneration is desired at all
' for a single project.
Dim userregenerate As MsgBoxResult
userregenerate = _
MsgBox("Should the project:" & vbNewLine & _
" " & selectedprojects(i).Name & vbNewLine & _
"be regenerated?" & vbNewLine & vbNewLine & _
"All file links and folders within the project will be removed," & _
vbNewLine & _
"and regenerated from the on-disk project structure.", _
MsgBoxStyle.YesNoCancel, _
"Really regenerate project: " & _
selectedprojects(i).Name & "?")
If userregenerate = MsgBoxResult.Cancel
Return
End If
If userregenerate = MsgBoxResult.No
Goto NextProject
End If
'==================================================================
' Delete old project items
'==================================================================
' Here, we just need to delete root project items.
' Since we do not know how indices change when deleting
' items, we do a copy to a save array first.
Dim items() As ProjectItem
Dim itemcount As Integer
itemcount = project.ProjectItems.Count
Redim items(itemcount - 1)
For j = 1 To project.ProjectItems.Count
items(j - 1) = project.ProjectItems.Item(j)
Next
For j = 0 To itemcount - 1
items(j).Remove()
Next
'==================================================================
' Regenerate project items from disk
'==================================================================
' First, retrieve the project folder
Dim projectfolder As String
projectfolder = _
Left(project.FullName, InStrRev(project.FullName, "") - 1)
' Then, starting with the project folder,
' add all files into our project.
' Note there is a bug here, we can't use the common DTE interfaces
' for managing project folders for VC projects.
' Thus, we need to go for the C++ specific project type instead.
Dim projectvc As VCProject
projectvc = project.Object
addItemsInFolder(projectfolder, "", projectvc, nothing, exts, 0)
NextProject:
Next
'==================================================================
' Recursively close all treeview items
'==================================================================
Dim solutionexplorer As UIHierarchy
solutionexplorer = _
DTE.Windows.Item(Constants.vsWindowKindSolutionExplorer).Object
For i = 1 To solutionexplorer.UIHierarchyItems.Count
closeTreeViewHelper _
(solutionexplorer.UIHierarchyItems.Item(i), solutionexplorer, _
selectedprojects, selectedprojectcount, 0)
Next
End Sub
'==========================================================================
' Directory management helper
'==========================================================================
Sub addItemsInFolder(basefolder As String, recursefolder As String, _
projectvc As VCProject, _
filtervc As VCFilter, exts() As String, _
depth As Integer)
' Put together base directory for searching,
' this needs to be the absolute directory within the
' project base directory.
Dim searchfolder As String
If recursefolder <> "" Then
searchfolder = basefolder & "" & recursefolder
Else
searchfolder = basefolder
End If
' First, handle files within the folder.
Dim filename As String
filename = Dir(searchfolder & "*")
Do While (filename <> "")
' Expand file to relative path within project
If recursefolder <> "" Then
filename = recursefolder & "" & filename
End If
' For each found file,
' check if it matches the provided extensions filter.
Dim i As Integer
For i = 0 To UBound(exts)
If Right(filename, Len(exts(i))) = exts(i) Then
' File matches, so either add it to the filter
' or the project itself
Dim file As VCFile
If filtervc Is Nothing Then
file = projectvc.AddFile(filename)
Else
file = filtervc.AddFile(filename)
End If
Goto filefinished
End If
Next
filefinished:
filename = Dir()
Loop
' Load all folder directories into some array -
' otherwise recursion won't do, since Dir() does
' not have a search handle.
Dim dirnames() As String
Dim dirnamecount As Integer
dirnamecount = 0
Dim dirname As String
dirname = Dir(searchfolder & "*", vbDirectory)
Do While (dirname <> "")
If (GetAttr(searchfolder & "" & dirname) And vbDirectory) = _
vbDirectory Then
dirnamecount += 1
Redim Preserve dirnames(dirnamecount - 1)
dirnames(dirnamecount - 1) = dirname
End If
dirname = Dir()
Loop
' Finally, create items for each folder, and
' recurse into subfolders.
Dim j As Integer
For j = 0 To dirnamecount - 1
Dim newfilter As VCFilter
If filtervc Is Nothing Then
newfilter = projectvc.AddFilter(dirnames(j))
Else
newfilter = filtervc.AddFilter(dirnames(j))
End If
Dim newrecurse As String
If recursefolder <> "" Then
newrecurse = recursefolder & "" & dirnames(j)
Else
newrecurse = dirnames(j)
End If
addItemsInFolder(basefolder, newrecurse, _
projectvc, newfilter, exts, depth + 1)
Next
End Sub
'==========================================================================
' Tree view closing helper
'==========================================================================
Sub closeTreeViewHelper(hierarchyitem As UIHierarchyItem, _
solutionexplorer As UIHierarchy, _
selectedprojects() As Project, _
selectedprojectcount As Integer, _
depth As Integer)
' Helper call for closing all but the lowest hierarchy items.
' This is a little bit more complicated than one would assume:
' First, we need to check if the given item is a folder.
' This is done by checking its number of children - empty folders
' are ignored.
' Then, the folder is first expanded, so we may close children
' folders - otherwise closing children doesn't work.
' Then, the folder is closed again.
' Folder closing is performed by selecting the folder and
' then simulating a mouse click event.
' Test if we are a valid folder - i.e. we have children.
' For leaf entries (files), we just abort here.
If hierarchyitem.UIHierarchyItems.Count = 0 Then
Return
End If
' On level 1 (projects level), we should ignore all projects
' that currently are not selected.
' This keeps the update from messing with unrelated projects.
If depth = 1 Then
Dim j As Integer
For j = 0 To selectedprojectcount - 1
If selectedprojects(j) Is hierarchyitem.Object Then
Goto projectselected
End If
Next
' Project not selected, so abort
Return
projectselected:
End If
' We are a folder, so see if we are expanded,
' if we aren't do the expansion now.
If Not hierarchyitem.UIHierarchyItems.Expanded Then
hierarchyitem.Select(vsUISelectionType.vsUISelectionTypeSelect)
solutionexplorer.DoDefaultAction()
End If
' Recurse on all children items
Dim i As Integer
For i = 1 To hierarchyitem.UIHierarchyItems.Count
closeTreeViewHelper _
(hierarchyitem.UIHierarchyItems.Item(i), solutionexplorer, _
selectedprojects, selectedprojectcount, depth + 1)
Next
' Finally, unexpand our object again,
' but only if it is not on depth = 1, so root project folders and
' files remain visible.
' (Depth = 0 -> Solution, Depth = 1 -> Projects)
If depth > 1 Then
hierarchyitem.Select(vsUISelectionType.vsUISelectionTypeSelect)
solutionexplorer.DoDefaultAction()
End If
End Sub
End Module