@@ -124,14 +124,15 @@ public int Execute()
124
124
125
125
Func < ProjectCollection , ProjectInstance > ? projectFactory = null ;
126
126
RunProperties ? cachedRunProperties = null ;
127
+ VirtualProjectBuildingCommand ? virtualCommand = null ;
127
128
if ( ShouldBuild )
128
129
{
129
130
if ( string . Equals ( "true" , launchSettings ? . DotNetRunMessages , StringComparison . OrdinalIgnoreCase ) )
130
131
{
131
132
Reporter . Output . WriteLine ( CliCommandStrings . RunCommandBuilding ) ;
132
133
}
133
134
134
- EnsureProjectIsBuilt ( out projectFactory , out cachedRunProperties ) ;
135
+ EnsureProjectIsBuilt ( out projectFactory , out cachedRunProperties , out virtualCommand ) ;
135
136
}
136
137
else
137
138
{
@@ -143,11 +144,11 @@ public int Execute()
143
144
if ( EntryPointFileFullPath is not null )
144
145
{
145
146
Debug . Assert ( ! ReadCodeFromStdin ) ;
146
- var command = CreateVirtualCommand ( ) ;
147
- command . MarkArtifactsFolderUsed ( ) ;
147
+ virtualCommand = CreateVirtualCommand ( ) ;
148
+ virtualCommand . MarkArtifactsFolderUsed ( ) ;
148
149
149
- var cacheEntry = command . GetPreviousCacheEntry ( ) ;
150
- projectFactory = CanUseRunPropertiesForCscBuiltProgram ( BuildLevel . None , cacheEntry ) ? null : command . CreateProjectInstance ;
150
+ var cacheEntry = virtualCommand . GetPreviousCacheEntry ( ) ;
151
+ projectFactory = CanUseRunPropertiesForCscBuiltProgram ( BuildLevel . None , cacheEntry ) ? null : virtualCommand . CreateProjectInstance ;
151
152
cachedRunProperties = cacheEntry ? . Run ;
152
153
}
153
154
}
@@ -163,6 +164,9 @@ public int Execute()
163
164
targetCommand . EnvironmentVariable ( name , value ) ;
164
165
}
165
166
167
+ // Send telemetry about the run operation
168
+ SendRunTelemetry ( launchSettings , virtualCommand ) ;
169
+
166
170
// Ignore Ctrl-C for the remainder of the command's execution
167
171
Console . CancelKeyPress += ( sender , e ) => { e . Cancel = true ; } ;
168
172
@@ -297,22 +301,23 @@ internal bool TryGetLaunchProfileSettingsIfNeeded(out ProjectLaunchSettingsModel
297
301
}
298
302
}
299
303
300
- private void EnsureProjectIsBuilt ( out Func < ProjectCollection , ProjectInstance > ? projectFactory , out RunProperties ? cachedRunProperties )
304
+ private void EnsureProjectIsBuilt ( out Func < ProjectCollection , ProjectInstance > ? projectFactory , out RunProperties ? cachedRunProperties , out VirtualProjectBuildingCommand ? virtualCommand )
301
305
{
302
306
int buildResult ;
303
307
if ( EntryPointFileFullPath is not null )
304
308
{
305
- var command = CreateVirtualCommand ( ) ;
306
- buildResult = command . Execute ( ) ;
307
- projectFactory = CanUseRunPropertiesForCscBuiltProgram ( command . LastBuild . Level , command . LastBuild . Cache ? . PreviousEntry ) ? null : command . CreateProjectInstance ;
308
- cachedRunProperties = command . LastBuild . Cache ? . CurrentEntry . Run ;
309
+ virtualCommand = CreateVirtualCommand ( ) ;
310
+ buildResult = virtualCommand . Execute ( ) ;
311
+ projectFactory = CanUseRunPropertiesForCscBuiltProgram ( virtualCommand . LastBuild . Level , virtualCommand . LastBuild . Cache ? . PreviousEntry ) ? null : virtualCommand . CreateProjectInstance ;
312
+ cachedRunProperties = virtualCommand . LastBuild . Cache ? . CurrentEntry . Run ;
309
313
}
310
314
else
311
315
{
312
316
Debug . Assert ( ProjectFileFullPath is not null ) ;
313
317
314
318
projectFactory = null ;
315
319
cachedRunProperties = null ;
320
+ virtualCommand = null ;
316
321
buildResult = new RestoringCommand (
317
322
MSBuildArgs . CloneWithExplicitArgs ( [ ProjectFileFullPath , .. MSBuildArgs . OtherMSBuildArgs ] ) ,
318
323
NoRestore ,
@@ -753,4 +758,135 @@ public static ParseResult ModifyParseResultForShorthandProjectOption(ParseResult
753
758
var newParseResult = Parser . Parse ( tokensToParse ) ;
754
759
return newParseResult ;
755
760
}
761
+
762
+ /// <summary>
763
+ /// Sends telemetry about the run operation.
764
+ /// </summary>
765
+ private void SendRunTelemetry (
766
+ ProjectLaunchSettingsModel ? launchSettings ,
767
+ VirtualProjectBuildingCommand ? virtualCommand )
768
+ {
769
+ try
770
+ {
771
+ if ( virtualCommand != null )
772
+ {
773
+ SendFileBasedTelemetry ( launchSettings , virtualCommand ) ;
774
+ }
775
+ else
776
+ {
777
+ SendProjectBasedTelemetry ( launchSettings ) ;
778
+ }
779
+ }
780
+ catch ( Exception ex )
781
+ {
782
+ // Silently ignore telemetry errors to not affect the run operation
783
+ if ( CommandLoggingContext . IsVerbose )
784
+ {
785
+ Reporter . Verbose . WriteLine ( $ "Failed to send run telemetry: { ex } ") ;
786
+ }
787
+ }
788
+ }
789
+
790
+ /// <summary>
791
+ /// Builds and sends telemetry data for file-based app runs.
792
+ /// </summary>
793
+ private void SendFileBasedTelemetry (
794
+ ProjectLaunchSettingsModel ? launchSettings ,
795
+ VirtualProjectBuildingCommand virtualCommand )
796
+ {
797
+ Debug . Assert ( EntryPointFileFullPath != null ) ;
798
+ var projectIdentifier = RunTelemetry . GetFileBasedIdentifier ( EntryPointFileFullPath , Sha256Hasher . Hash ) ;
799
+
800
+ var directives = virtualCommand . Directives ;
801
+ var sdkCount = RunTelemetry . CountSdks ( directives ) ;
802
+ var packageReferenceCount = RunTelemetry . CountPackageReferences ( directives ) ;
803
+ var projectReferenceCount = RunTelemetry . CountProjectReferences ( directives ) ;
804
+ var additionalPropertiesCount = RunTelemetry . CountAdditionalProperties ( directives ) ;
805
+
806
+ RunTelemetry . TrackRunEvent (
807
+ isFileBased : true ,
808
+ projectIdentifier : projectIdentifier ,
809
+ launchProfile : LaunchProfile ,
810
+ noLaunchProfile : NoLaunchProfile ,
811
+ launchSettings : launchSettings ,
812
+ sdkCount : sdkCount ,
813
+ packageReferenceCount : packageReferenceCount ,
814
+ projectReferenceCount : projectReferenceCount ,
815
+ additionalPropertiesCount : additionalPropertiesCount ,
816
+ usedMSBuild : virtualCommand . LastBuild . Level is BuildLevel . All ,
817
+ usedRoslynCompiler : virtualCommand . LastBuild . Level is BuildLevel . Csc ) ;
818
+ }
819
+
820
+ /// <summary>
821
+ /// Builds and sends telemetry data for project-based app runs.
822
+ /// </summary>
823
+ private void SendProjectBasedTelemetry ( ProjectLaunchSettingsModel ? launchSettings )
824
+ {
825
+ Debug . Assert ( ProjectFileFullPath != null ) ;
826
+ var projectIdentifier = RunTelemetry . GetProjectBasedIdentifier ( ProjectFileFullPath , GetRepositoryRoot ( ) , Sha256Hasher . Hash ) ;
827
+
828
+ // Get package and project reference counts for project-based apps
829
+ int packageReferenceCount = 0 ;
830
+ int projectReferenceCount = 0 ;
831
+
832
+ // Try to get project information for telemetry if we built the project
833
+ if ( ShouldBuild )
834
+ {
835
+ try
836
+ {
837
+ var globalProperties = MSBuildArgs . GlobalProperties ? . ToDictionary ( ) ?? new Dictionary < string , string > ( StringComparer . OrdinalIgnoreCase ) ;
838
+ globalProperties [ Constants . EnableDefaultItems ] = "false" ;
839
+ globalProperties [ Constants . MSBuildExtensionsPath ] = AppContext . BaseDirectory ;
840
+
841
+ using var collection = new ProjectCollection ( globalProperties : globalProperties ) ;
842
+ var project = collection . LoadProject ( ProjectFileFullPath ) . CreateProjectInstance ( ) ;
843
+
844
+ packageReferenceCount = RunTelemetry . CountPackageReferences ( project ) ;
845
+ projectReferenceCount = RunTelemetry . CountProjectReferences ( project ) ;
846
+ }
847
+ catch
848
+ {
849
+ // If project evaluation fails for telemetry, use defaults
850
+ // We don't want telemetry collection to affect the run operation
851
+ }
852
+ }
853
+
854
+ RunTelemetry . TrackRunEvent (
855
+ isFileBased : false ,
856
+ projectIdentifier : projectIdentifier ,
857
+ launchProfile : LaunchProfile ,
858
+ noLaunchProfile : NoLaunchProfile ,
859
+ launchSettings : launchSettings ,
860
+ packageReferenceCount : packageReferenceCount ,
861
+ projectReferenceCount : projectReferenceCount ) ;
862
+ }
863
+
864
+ /// <summary>
865
+ /// Attempts to find the repository root directory.
866
+ /// </summary>
867
+ /// <returns>Repository root path if found, null otherwise</returns>
868
+ private string ? GetRepositoryRoot ( )
869
+ {
870
+ try
871
+ {
872
+ var currentDir = ProjectFileFullPath != null
873
+ ? Path . GetDirectoryName ( ProjectFileFullPath )
874
+ : Directory . GetCurrentDirectory ( ) ;
875
+
876
+ while ( currentDir != null )
877
+ {
878
+ if ( Directory . Exists ( Path . Combine ( currentDir , ".git" ) ) )
879
+ {
880
+ return currentDir ;
881
+ }
882
+ currentDir = Directory . GetParent ( currentDir ) ? . FullName ;
883
+ }
884
+ }
885
+ catch
886
+ {
887
+ // Ignore errors when trying to find repo root
888
+ }
889
+
890
+ return null ;
891
+ }
756
892
}
0 commit comments