@@ -1197,7 +1197,7 @@ public virtual int QueryDebugTargets(uint grfLaunch, uint cTargets, VsDebugTarge
11971197public virtual int OpenOutputGroup ( string szCanonicalName , out IVsOutputGroup ppIVsOutputGroup )
11981198{
11991199ppIVsOutputGroup = null ;
1200- // Search through our list of groups to find the one they are lookingforgroupName
1200+ // Search through our list of groups to find the one they are lookingfor groupName
12011201foreach ( OutputGroup group in OutputGroups )
12021202{
12031203string groupName ;
@@ -1458,7 +1458,7 @@ private static bool IsPossibleOutputGroup(string groupName)
14581458return null ;
14591459}
14601460
1461- internal bool GetUTDCheckInputs ( ref HashSet < string > inputs )
1461+ internal bool GetUTDCheckInputs ( HashSet < string > inputs )
14621462{
14631463// the project file itself
14641464inputs . Add ( Utilities . CanonicalizeFileNameNoThrow ( this . project . BuildProject . FullPath ) ) ;
@@ -1527,8 +1527,10 @@ internal bool GetUTDCheckInputs(ref HashSet<string> inputs)
15271527return true ;
15281528}
15291529
1530- internal bool GetUTDCheckOutputs ( ref HashSet < string > outputs , HashSet < string > inputs )
1530+ internal void GetUTDCheckOutputs ( HashSet < string > inputs , HashSet < string > outputs , out List < Tuple < string , string > > preserveNewestOutputs )
15311531{
1532+ preserveNewestOutputs = new List < Tuple < string , string > > ( ) ;
1533+
15321534// Output groups give us the paths to the following outputs
15331535// result EXE or DLL in "obj" dir
15341536// PDB file in "obj" dir (if project is configured to create this)
@@ -1546,15 +1548,29 @@ internal bool GetUTDCheckOutputs(ref HashSet<string> outputs, HashSet<string> in
15461548var outputAssembly = this . project . GetOutputAssembly ( this . ConfigCanonicalName ) ;
15471549outputs . Add ( Utilities . CanonicalizeFileNameNoThrow ( outputAssembly ) ) ;
15481550
1551+ bool isExe = outputAssembly . EndsWith ( ".exe" , StringComparison . OrdinalIgnoreCase ) ;
1552+
15491553// final PDB path
15501554if ( this . DebugSymbols &&
1551- ( outputAssembly . EndsWith ( ".exe" , StringComparison . OrdinalIgnoreCase ) || outputAssembly . EndsWith ( ".dll" , StringComparison . OrdinalIgnoreCase ) ) )
1555+ ( isExe || outputAssembly . EndsWith ( ".dll" , StringComparison . OrdinalIgnoreCase ) ) )
15521556{
15531557var pdbPath = outputAssembly . Remove ( outputAssembly . Length - 4 ) + ".pdb" ;
15541558outputs . Add ( Utilities . CanonicalizeFileNameNoThrow ( pdbPath ) ) ;
15551559}
15561560
1557- return true ;
1561+ if ( isExe )
1562+ {
1563+ var appConfig = inputs . FirstOrDefault ( x=> String . Compare ( Path . GetFileName ( x ) , "app.config" , StringComparison . OrdinalIgnoreCase ) == 0 ) ;
1564+ if ( appConfig != null )
1565+ {
1566+ // the app.config is not removed from the inputs to maintain
1567+ // the same behavior of a C# project:
1568+ // When a app.config is changed, after the build, the project
1569+ // is not up-to-date until a rebuild
1570+ var exeConfig = Utilities . CanonicalizeFileNameNoThrow ( outputAssembly + ".config" ) ;
1571+ preserveNewestOutputs . Add ( Tuple . Create ( appConfig , exeConfig ) ) ;
1572+ }
1573+ }
15581574}
15591575
15601576// there is a well-known property users can specify that signals for UTD check to be disabled
@@ -1584,12 +1600,12 @@ internal bool IsUpToDate(OutputWindowLogger logger, bool testing)
15841600}
15851601
15861602var inputs = new HashSet < string > ( StringComparer . OrdinalIgnoreCase ) ;
1587- if ( ! GetUTDCheckInputs ( ref inputs ) )
1603+ if ( ! GetUTDCheckInputs ( inputs ) )
15881604return false ;
15891605
15901606var outputs = new HashSet < string > ( StringComparer . OrdinalIgnoreCase ) ;
1591- if ( ! GetUTDCheckOutputs ( ref outputs , inputs ) )
1592- return false ;
1607+ List < Tuple < string , string > > preserveNewestOutputs ;
1608+ GetUTDCheckOutputs ( inputs , outputs , out preserveNewestOutputs ) ;
15931609
15941610// determine the oldest output timestamp
15951611DateTime stalestOutputTime = DateTime . MaxValue . ToUniversalTime ( ) ;
@@ -1625,13 +1641,48 @@ internal bool IsUpToDate(OutputWindowLogger logger, bool testing)
16251641freshestInputTime = timeStamp . Value ;
16261642}
16271643
1644+ // check 1-1 Preserve Newest mappings
1645+ foreach ( var kv in preserveNewestOutputs )
1646+ {
1647+ if ( ! IsUpToDatePreserveNewest ( logger , TryGetLastWriteTimeUtc , kv . Item1 , kv . Item2 ) )
1648+ return false ;
1649+ }
1650+
16281651logger . WriteLine ( "Freshest input: {0}" , freshestInputTime . ToLocalTime ( ) ) ;
16291652logger . WriteLine ( "Stalest output: {0}" , stalestOutputTime . ToLocalTime ( ) ) ;
16301653logger . WriteLine ( "Up to date: {0}" , freshestInputTime <= stalestOutputTime ) ;
16311654
1632- // if all outputs are younger than allinuts , we are up to date
1655+ // if all outputs are younger than allinputs , we are up to date
16331656return freshestInputTime <= stalestOutputTime ;
16341657}
1658+
1659+ public static bool IsUpToDatePreserveNewest ( OutputWindowLogger logger , Func < string , OutputWindowLogger , DateTime ? > tryGetLastWriteTimeUtc , string input , string output )
1660+ {
1661+ var inputTime = tryGetLastWriteTimeUtc ( input , logger ) ;
1662+ if ( ! inputTime . HasValue )
1663+ {
1664+ logger . WriteLine ( "Declaring project NOT up to date, can't find expected input {0}" , input ) ;
1665+ return false ;
1666+ }
1667+
1668+ var outputTime = tryGetLastWriteTimeUtc ( output , logger ) ;
1669+ if ( ! outputTime . HasValue )
1670+ {
1671+ logger . WriteLine ( "Declaring project NOT up to date, can't find expected output {0}" , output ) ;
1672+ return false ;
1673+ }
1674+
1675+ var inputTimeValue = inputTime . Value ;
1676+ var outputTimeValue = outputTime . Value ;
1677+
1678+ if ( outputTimeValue < inputTimeValue )
1679+ {
1680+ logger . WriteLine ( "Declaring project NOT up to date, ouput {0} is stale" , output ) ;
1681+ return false ;
1682+ }
1683+
1684+ return true ;
1685+ }
16351686}
16361687
16371688internal class ClassLibraryCannotBeStartedDirectlyException : COMException