动态确定构建/触发器事件上的所有解决方案,项目和配置,并使用MSBuild从命令行/脚本调用它们

时间:2009-06-22 03:30:25

标签: c++ visual-studio-2008 msbuild

我们在设置构建自动化方面还有点早,现在只使用bat文件来获取Win32 C ++解决方案。我们有大约4个解决方案,每个解决方案都有几个vcproj文件。

每次添加新的解决方案或配置时,我都必须更新bat文件以反映要使用MSBuild调用的新解决方案或配置。

我想也许更容易编写一个解析给定路径(及其子树)中所有sln文件的工具,然后解析它为配置引用的所有项目文件,然后以这种方式调用所有构建。

有一种简单的方法吗?

真的这是两个问题:

  1. 如何告诉MSBuild只在子树中构建所有解决方案? (我可以对它们进行搜索 - 这是我想写的一个简单工具)

  2. 如何告诉MSBuild构建解决方案/ vcproj的所有配置?

  3. 我们正在使用MSBuild,bat文件,vc2008,c ++

2 个答案:

答案 0 :(得分:1)

作为PowerShell粉丝,以下重新格式化的单行可能会有所帮助:

Get-ChildItem -Recurse -Include *.sln | 
ForEach-Object {  
    $Solution = $_.FullName
    Get-Content $_ | 
    ForEach-Object { 
        if($_ -match '\{[^\}]+[^=]+= ([^\{\s]*)$') { 
            $matches[1] 
        }
    } | Sort-Object -Unique | 
    ForEach-Object { 
        $config =  ([string]$_).Split([char]'|')
        & "$env:windir\Microsoft.NET\Framework\v3.5\msbuild.exe" $Solution /p:Configuration="$($config[0])" /p:Platform="$($config[1])" 
    }
}

此脚本可以保存为ps1文件或粘贴为个人资料中的功能。解释它的作用:

  1. 查找当前目录中及以下的所有.sln文件
  2. 解析提取配置和平台值的sln
  3. 为每个唯一的配置平台组合调用带有给定解决方案的msbuild
  4. - 编辑:Forgot Split-String是PSCX的一部分,最好使用[string] 我希望这有帮助。如果您仍想使用MsBuild,它通过“。\ ** \ * .sln”语法支持递归形式的文件glob运算符,请参阅here了解详细信息。 MsBuild还提供了MsBuild任务,可用于构建一组解决方案。我不知道如何在msbuild中轻松地从解决方案中获取所有“已知”配置。因此我选择了PowerShell路由。

答案 1 :(得分:0)

可以通过API访问Microsoft构建框架。但我不确定是否只能通过批处理文件来完成。恕我直言,最简单的方法是通过将一个插件(使用VB.NET)写入VS IDE来自动化构建。可以从批处理文件中调用此插件。我们使用addin方法自动化VC ++构建。在我们的例子中没有别的办法因为我们必须自动化Qt的 moc 过程。我认为它也会更灵活。

我工作的构建系统(至少在VS 2005中)非常错误。请参阅此link,它将帮助您理解陷阱,并提供有关如何遍历解决方案文件的代码片段。 一旦安装了插件,就可以创建一个小的exe,它通过addin调用构建。这个exe可以从批处理文件中调用。

示例VS Automator exe调用addin方法

''' Summary: Console App to automate MSVS build with special regard to Addin
Imports System
Imports Extensibility
Imports EnvDTE
Imports EnvDTE80
Imports System.Reflection
Imports System.Windows.Forms
Imports System.IO
'''<summary>Module class that automates launching MSVS in silent mode. It disables all user actions. A hidden window is used for MSVS automation. This should work for VS 2008 as well.</summary>
'''<remarks>An STA Thread is used for this console app. Refer http://msmvps.com/blogs/carlosq/archive/2007/10/11/frustrations-with-command-line-add-ins-for-visual-studio.aspx for details </remarks>
Module VSAutomaton
    Private Enum VisualStudioSolVersion
        Unknown = 0
        VSNET2002 = 1
        VSNET2003 = 2
        VS2005 = 3
        VS2008 = 4
    End Enum
    <STAThread()> _
    Sub Main(ByVal args() As String)
        Const ADDIN_PROGID As String = "AddIn-Name.Connect"
        Const ADDIN_METHOD As String = "SyncSolutionBatch" ' name of your method in addin code
        Dim dte As EnvDTE.DTE = Nothing
        Dim dteType As Type
        Dim commandLineAddIn As AddIn-Name.Connect = Nothing
        Dim solutionFullFileName As String
        Dim solutionFolder As String
        Dim solutionName As String
        Dim logFullFileName As String
        Dim buildLogFile As String = Nothing
        Dim buildConfig As String = Nothing
        Dim connectObject As Object = Nothing
        Dim connectObjectType As Type
        Dim version As VisualStudioSolVersion
        Dim progID As String
        Dim executableName As String
        Dim addIn As EnvDTE.AddIn
        Dim msgFilter As MessageFilter.MessageFilter = Nothing

        Try
            msgFilter = New MessageFilter.MessageFilter
            If args.Length = 0 Then
                executableName = IO.Path.GetFileName(System.Reflection.Assembly.GetExecutingAssembly.Location)
                ReportError("Usage: " & executableName & " solution_file_name.sln")
            Else
                solutionFullFileName = args(0) ' project solution file
                If Not IO.File.Exists(solutionFullFileName) Then
                    ReportError("Solution file '" & solutionFullFileName & "' does not exist.")
                Else
                    solutionFolder = IO.Path.GetDirectoryName(solutionFullFileName)
                    solutionName = IO.Path.GetFileNameWithoutExtension(solutionFullFileName)
                    logFullFileName = IO.Path.Combine(solutionFolder, solutionName & ".log")
                    If IO.File.Exists(logFullFileName) Then
                        IO.File.Delete(logFullFileName)
                    End If
                    version = GetSolutionVersion(solutionFullFileName)
                    If version = VisualStudioSolVersion.Unknown Then
                        ReportError("The format version of the solution file is not supported.")
                    Else
                        progID = GetVisualStudioProgID(version)
                        dteType = System.Type.GetTypeFromProgID(progID)
                        If dteType Is Nothing Then
                            ReportError("Could not find the ActiveX Server for ProgID '" & progID & "'. Likely the proper version of Visual Studio is not installed.")
                        Else
                            dte = DirectCast(System.Activator.CreateInstance(dteType), EnvDTE.DTE)
                            dte.SuppressUI = True
                            dte.UserControl = False
                            addIn = GetAddInByProgID(dte, ADDIN_PROGID)
                            If addIn Is Nothing Then
                                ReportError("The Add-in " & ADDIN_PROGID & " was not found in Visual Studio.")
                            Else
                                addIn.Connected = True
                                connectObject = addIn.Object
                                connectObjectType = connectObject.GetType
                                ' So a copy of the same DLL is necessary in the same dir as this app. exe
                                connectObjectType.InvokeMember(ADDIN_METHOD, Reflection.BindingFlags.InvokeMethod Or Reflection.BindingFlags.Static Or Reflection.BindingFlags.Public, Nothing, connectObject, New String() {solutionFullFileName})
                            End If
                        End If
                    End If
                End If
            End If

        Catch ex As Exception
            ReportError(ex.ToString)
        Finally
            If Not (dte Is Nothing) Then
                Try
                    dte.Quit() 
                Catch ex As Exception
                End Try
            End If
            If Not (msgFilter Is Nothing) Then
                ' this is a tricky aspect. We do not want leaks but .NET can sometimes be extra smart
                msgFilter.Dispose() 'If the GC decides to re-collect the garbage from this app, then a crash may result
                ' but this is the drawback of indeterministic destruction semantics
            End If
        End Try
    End Sub

    Private Sub ReportError(ByVal msg As String)
#If DEBUG Then
        MsgBox(msg)
#End If
        Console.WriteLine(msg)
    End Sub
    Private Function GetAddInByProgID(ByVal dte As EnvDTE.DTE, ByVal addinProgID As String) As EnvDTE.AddIn
        Dim addinResult As EnvDTE.AddIn = Nothing
        Dim addin As EnvDTE.AddIn
        For Each addin In dte.AddIns
            If addin.ProgID = addinProgID Then
                addinResult = addin
                Exit For
            End If
        Next
        Return addinResult
    End Function
    Private Function GetSolutionVersion(ByVal solutionFullFileName As String) As VisualStudioSolVersion
        Dim version As VisualStudioSolVersion = VisualStudioSolVersion.Unknown
        Dim solutionStreamReader As IO.StreamReader = Nothing
        Dim firstLine As String = Nothing
        Dim format As String
            Try
                solutionStreamReader = New IO.StreamReader(solutionFullFileName)
                firstLine = solutionStreamReader.ReadLine()
                format = firstLine.Substring(firstLine.LastIndexOf(" ")).Trim
                Select Case format
                    Case "7.00"
                        version = VisualStudioSolVersion.VSNET2002
                    Case "8.00"
                        version = VisualStudioSolVersion.VSNET2003
                    Case "9.00"
                        version = VisualStudioSolVersion.VS2005
                    Case "10.00"
                        version = VisualStudioSolVersion.VS2008
                End Select
            Finally
                If Not (solutionStreamReader Is Nothing) Then
                    solutionStreamReader.Close()
                End If
            End Try
        Return version
    End Function
    Private Function GetVisualStudioProgID(ByVal version As VisualStudioSolVersion) As String
        Dim progID As String = ""
        Select Case version
            Case VisualStudioSolVersion.VSNET2002
                progID = "VisualStudio.DTE.7"
            Case VisualStudioSolVersion.VSNET2003
                progID = "VisualStudio.DTE.7.1"
            Case VisualStudioSolVersion.VS2005
                progID = "VisualStudio.DTE.8.0"
            Case VisualStudioSolVersion.VS2008
                progID = "VisualStudio.DTE.9.0"
        End Select
        Return progID
    End Function

End Module

用于调用VS automator exe的示例批处理文件:

@echo off
:: --Usage:       $>BatchFileName.bat "<project_name(.sln)>" {Release | Debug} [ Make ]
::                Please remember the "double-quotes". 


REM -- check for blank input
if x%1%x == xx goto InputError
if x%2%x == xx goto InputError

echo Automating MSVS-Build for %1% ...
echo .

set arg1=%1%
REM -- remove quotes
for /f "useback tokens=*" %%a in ('%arg1%') do set match=%%~a
set slnFile=%match:~-4%
if %slnFile% == .sln goto lbl_FileOK 

:lbl_FileOK

REM build configuration and output file
set SOLFILE=%1%
set BUILDCONFIG=%2%
set CLEANWSOBJS=%3%


REM -- Read necessary registry entries
REM --- Read MSVS installation dir
regedit /e A$B$C$.bxt "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\8.0\Setup\VS"
find "VS7CommonDir" <A$B$C$.bxt>A$B$C$.bat
goto :1st


:1st
for /F "tokens=1* delims==" %%A in ('TYPE A$B$C$.bat ^| find "VS7CommonDir"') do set vscomdir=%%B
set vscomdir=%vscomdir:"=%
REM -- Initialize the MSVS environment
set VSENV="%vscomdir%Tools\vsvars32.bat"
call %VSENV% > nul

REM -- remove quotes
for /f "useback tokens=*" %%a in ('%SOLFILE%') do set str=%%~a
set LastFolder=%str%
REM -- Extract the project name
if "%LastFolder:~-1%"=="\" set LastFolder=%LastFolder:~0,-1%
for %%a in ("%LastFolder%") do set LastFolder=%%~nxa
set flname=%LastFolder:.shared=.sln%
set tmpfile=%solPath%\%flname%

REM --- Check if the target '.sln' already exists, if yes delete
if EXIST %NEWSOLFILE% DEL /Q %NEWSOLFILE%
     REM -- use the addin functionality 
    VSAutomator.exe %SOLFILE%

    REM --- create log file as projectname_buildconfig.log
    set tmplog=%NEWSOLFILE:.sln=%
    set OUTLOGFILE=%tmplog%_%BUILDCONFIG%.log

    REM -- Now build the newly ready .sln file
    echo .
    echo Building Solution file - %NEWSOLFILE%, Output log file - %OUTLOGFILE%
    echo .

    if x%CLEANWSOBJS%x == xMakex goto JustBuild1
    devenv.com /useenv %NEWSOLFILE% /CLEAN %BUILDCONFIG% /OUT %OUTLOGFILE% > nul

:JustBuild1
    devenv.com /useenv %NEWSOLFILE% /BUILD %BUILDCONFIG% /OUT %OUTLOGFILE% > nul

请记住,上面提到的代码可能并不完美,因为我从我的POC中提到了我在遇到类似自动化问题时所做的事情。