@@ -115,9 +115,10 @@ class Msbuild extends ConventionTask {
115115 version. startsWith(' 4.' ) || version == ' 14.0' || version == ' 12.0' )
116116
117117 if (useOldMsbuild) {
118+ def msbuildType = OperatingSystem . current(). windows ? " Windows MSBuild" : " Mono's MSBuild"
118119 logger. warn(" Skipping project file parsing for old MSBuild version (${ version} ). " +
119120 " ProjectFileParser uses .NET SDK MSBuild which cannot parse old-style projects. " +
120- " The build will proceed using Mono's MSBuild ." )
121+ " The build will proceed using ${ msbuildType } ." )
121122 parseProject = false
122123 // Initialize allProjects as empty map to prevent NPE
123124 if (allProjects == null ) {
@@ -132,8 +133,11 @@ class Msbuild extends ConventionTask {
132133 resolveProject()
133134 }
134135 } catch (OldProjectFormatException e) {
135- // Old project format detected - skip parsing
136- logger. warn(" Old-style project format detected. Build will proceed using Mono's MSBuild." )
136+ // Old project format detected or dotnet not available - skip parsing
137+ def msbuildType = OperatingSystem . current(). windows ? " Windows MSBuild" : " Mono's MSBuild"
138+ def reason = e. message?. contains(" dotnet command not available" ) ?
139+ " dotnet command not available" : " old-style project format detected"
140+ logger. warn(" ${ reason} . Project file parsing will be skipped. Build will proceed using ${ msbuildType} ." )
137141 parseProject = false
138142 if (allProjects == null ) {
139143 allProjects = [:]
@@ -153,8 +157,9 @@ class Msbuild extends ConventionTask {
153157 combinedError. contains(' invalidprojectfileexception' ) ||
154158 combinedError. contains(' microsoft.build.exceptions' ) ||
155159 (combinedError. contains(' failed to parse project' ) && combinedError. contains(' exit code: 255' ))) {
160+ def msbuildType = OperatingSystem . current(). windows ? " Windows MSBuild" : " Mono's MSBuild"
156161 logger. warn(" Failed to parse project file (old-style project detected, .NET SDK MSBuild cannot parse it). " +
157- " Build will proceed using Mono's MSBuild ." )
162+ " Build will proceed using ${ msbuildType } ." )
158163 parseProject = false
159164 if (allProjects == null ) {
160165 allProjects = [:]
@@ -224,13 +229,38 @@ class Msbuild extends ConventionTask {
224229 def parserDll = new File (tempDir, ' ProjectFileParser.dll' )
225230 def parseOutputStream = new ByteArrayOutputStream ()
226231 def errorOutputStream = new ByteArrayOutputStream ()
227- def parser = execOps. exec { exec ->
228- exec. commandLine(' dotnet' , ' --roll-forward' , ' Major' , parserDll)
229- exec. args(file. toString(), JsonOutput . toJson(getInitProperties()). replace(' "' , ' \' ' ))
230- exec. standardOutput = parseOutputStream
231- exec. errorOutput = errorOutputStream
232- // We want to be able to print the details of what actually failed, otherwise we won't have this info
233- exec. ignoreExitValue = true
232+ def parser
233+ try {
234+ parser = execOps. exec { exec ->
235+ exec. commandLine(' dotnet' , ' --roll-forward' , ' Major' , parserDll)
236+ exec. args(file. toString(), JsonOutput . toJson(getInitProperties()). replace(' "' , ' \' ' ))
237+ exec. standardOutput = parseOutputStream
238+ exec. errorOutput = errorOutputStream
239+ // We want to be able to print the details of what actually failed, otherwise we won't have this info
240+ exec. ignoreExitValue = true
241+ }
242+ } catch (Exception e) {
243+ // Handle process start failures (e.g., dotnet command not found, permission denied, etc.)
244+ def errorMsg = e. message?. toLowerCase() ?: ' '
245+ def isCommandNotFound = errorMsg. contains(' command' ) && errorMsg. contains(' not found' ) ||
246+ errorMsg. contains(' cannot run program' ) ||
247+ errorMsg. contains(' no such file' ) ||
248+ errorMsg. contains(' executable not found' ) ||
249+ errorMsg. contains(' not recognized' ) || // Windows: "is not recognized as an internal or external command"
250+ errorMsg. contains(' cannot find the file' ) || // Windows alternative
251+ (errorMsg. contains(' system cannot find' ) && errorMsg. contains(' specified' )) // Windows error
252+
253+ if (isCommandNotFound) {
254+ logger. warn(" dotnet command not found or cannot be executed. " +
255+ " Project file parsing will be skipped. " +
256+ " Make sure dotnet SDK is installed and available in PATH. " +
257+ " Error: ${ e.message} " )
258+ throw new OldProjectFormatException (" dotnet command not available - skipping project parsing" )
259+ } else {
260+ // Re-throw other exceptions (IO errors, etc.)
261+ logger. error(" Failed to execute ProjectFileParser: ${ e.message} " )
262+ throw new GradleException (" Failed to execute ProjectFileParser: ${ e.message} " , e)
263+ }
234264 }
235265 if (parser. exitValue != 0 ) {
236266 def errorOutput = errorOutputStream. toString()
0 commit comments