Skip to content

Commit e71d51c

Browse files
committed
Fix NuGet restore on Ubuntu: skip MSBuildPath to avoid Mono/.NET SDK assembly loading issues
- Skip -MSBuildPath on non-Windows to prevent Mono from trying to load .NET SDK assemblies - Add optional useDotnetRestore property (default: false) to opt-in to 'dotnet restore' - Maintain backward compatibility: default behavior unchanged (uses nuget.exe with Mono) - Fixes Ubuntu error: 'Can't find custom attr constructor' when Mono tries to load .NET 7.0 assemblies
1 parent cedd629 commit e71d51c

File tree

2 files changed

+111
-52
lines changed

2 files changed

+111
-52
lines changed

src/main/groovy/com/ullink/BaseNuGet.groovy

Lines changed: 98 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -215,17 +215,90 @@ class BaseNuGet extends Exec {
215215
// Set deduplicated args
216216
setArgs(normalizedArgs)
217217

218-
if (isFamily(FAMILY_WINDOWS)) {
219-
executable = localNuget.absolutePath
220-
} else {
221-
executable = "mono"
222-
// Prepend nuget.exe path to args for mono execution
223-
setArgs([localNuget.absolutePath] + normalizedArgs)
218+
// Check if we should use dotnet restore instead of nuget.exe on non-Windows
219+
// Only use dotnet restore if explicitly enabled via useDotnetRestore property
220+
def useDotnetRestore = false
221+
if (!isFamily(FAMILY_WINDOWS) && this instanceof NuGetRestore) {
222+
try {
223+
if (this.hasProperty('useDotnetRestore') && this.useDotnetRestore) {
224+
def dotnetPath = findDotnetPath()
225+
if (dotnetPath) {
226+
useDotnetRestore = true
227+
} else {
228+
project.logger.warn("useDotnetRestore is enabled but 'dotnet' command not found. Falling back to nuget.exe with Mono.")
229+
}
230+
}
231+
} catch (Exception e) {
232+
project.logger.debug("Could not check useDotnetRestore property: ${e.message}")
233+
}
234+
235+
if (useDotnetRestore) {
236+
executable = dotnetPath
237+
// Convert nuget.exe restore args to dotnet restore args
238+
def dotnetArgs = ['restore']
239+
// Add solution file if present
240+
if (this.hasProperty('solutionFile') && this.solutionFile) {
241+
def solutionPath = this.solutionFile instanceof File ? this.solutionFile.absolutePath : project.file(this.solutionFile).absolutePath
242+
dotnetArgs.add(solutionPath)
243+
}
244+
// Add packages.config file if present (dotnet restore doesn't support this directly, but we'll try)
245+
if (this.hasProperty('packagesConfigFile') && this.packagesConfigFile) {
246+
project.logger.warn("packages.config files are not directly supported by 'dotnet restore'. Consider migrating to PackageReference format.")
247+
}
248+
// Add sources
249+
if (this.hasProperty('sources') && !this.sources.isEmpty()) {
250+
this.sources.each { source ->
251+
dotnetArgs.add('--source')
252+
dotnetArgs.add(source)
253+
}
254+
}
255+
// Add config file
256+
if (this.hasProperty('configFile') && this.configFile) {
257+
dotnetArgs.add('--configfile')
258+
dotnetArgs.add(this.configFile.absolutePath)
259+
}
260+
// Add packages directory
261+
if (this.hasProperty('packagesDirectory') && this.packagesDirectory) {
262+
dotnetArgs.add('--packages')
263+
dotnetArgs.add(this.packagesDirectory)
264+
}
265+
// Add solution directory
266+
if (this.hasProperty('solutionDirectory') && this.solutionDirectory) {
267+
dotnetArgs.add('--solution-directory')
268+
dotnetArgs.add(this.solutionDirectory.absolutePath)
269+
}
270+
// Disable parallel processing
271+
if (this.hasProperty('disableParallelProcessing') && this.disableParallelProcessing) {
272+
dotnetArgs.add('--no-parallel')
273+
}
274+
setArgs(dotnetArgs)
275+
project.logger.info("Using 'dotnet restore' instead of 'nuget.exe' (dotnet SDK detected)")
276+
}
224277
}
225278

226-
// Add flags after setting the base args
227-
args '-NonInteractive'
228-
args '-Verbosity', (verbosity ?: getNugetVerbosity())
279+
if (!useDotnetRestore) {
280+
if (isFamily(FAMILY_WINDOWS)) {
281+
executable = localNuget.absolutePath
282+
} else {
283+
executable = "mono"
284+
// Prepend nuget.exe path to args for mono execution
285+
setArgs([localNuget.absolutePath] + normalizedArgs)
286+
}
287+
288+
// Add nuget.exe-specific flags (not needed for dotnet restore)
289+
args '-NonInteractive'
290+
args '-Verbosity', (verbosity ?: getNugetVerbosity())
291+
} else {
292+
// For dotnet restore, add verbosity if needed (different format)
293+
def verbosityLevel = verbosity ?: getNugetVerbosity()
294+
if (verbosityLevel == 'detailed') {
295+
args '--verbosity', 'detailed'
296+
} else if (verbosityLevel == 'normal') {
297+
args '--verbosity', 'normal'
298+
} else if (verbosityLevel == 'quiet') {
299+
args '--verbosity', 'quiet'
300+
}
301+
}
229302

230303
project.logger.info("Final args before execution: ${getArgs().toList()}")
231304

@@ -359,4 +432,20 @@ class BaseNuGet extends Exec {
359432
if (logger.infoEnabled) return 'normal'
360433
return 'quiet'
361434
}
435+
436+
/**
437+
* Find the dotnet executable path (for use with dotnet restore)
438+
*/
439+
protected String findDotnetPath() {
440+
try {
441+
def process = ['which', 'dotnet'].execute()
442+
process.waitFor()
443+
if (process.exitValue() == 0) {
444+
return process.text.trim()
445+
}
446+
} catch (Exception e) {
447+
// dotnet not found
448+
}
449+
return null
450+
}
362451
}

src/main/groovy/com/ullink/NuGetRestore.groovy

Lines changed: 13 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ class NuGetRestore extends BaseNuGet {
4444
def packagesDirectory
4545
@Input
4646
def ignoreFailuresOnNonWindows = false
47+
@Input
48+
def useDotnetRestore = false // Set to true to use 'dotnet restore' instead of 'nuget.exe' on non-Windows
4749

4850
NuGetRestore() {
4951
super('restore')
@@ -106,38 +108,22 @@ class NuGetRestore extends BaseNuGet {
106108
if (solutionDirectory) args '-SolutionDirectory', solutionDirectory
107109
if (disableParallelProcessing) args '-DisableParallelProcessing'
108110

109-
// On non-Windows platforms, try to use modern .NET SDK's MSBuild if available
111+
// On non-Windows platforms, skip MSBuildPath to avoid Mono/.NET SDK assembly loading issues
112+
// When nuget.exe runs under Mono, it cannot load .NET SDK assemblies (e.g., .NET 7.0's Microsoft.Build.dll)
113+
// So we skip -MSBuildPath on non-Windows to let NuGet use Mono's xbuild/MSBuild by default
110114
if (!isFamily(FAMILY_WINDOWS)) {
111-
// If msBuildPath is not explicitly set, try to auto-detect dotnet SDK MSBuild
112-
if (!msBuildPath) {
113-
def dotnetPath = findDotnetPath()
114-
if (dotnetPath) {
115-
def dotnetMsbuildPath = findDotnetMsbuildPath(dotnetPath)
116-
if (dotnetMsbuildPath) {
117-
msBuildPath = dotnetMsbuildPath
118-
project.logger.debug("Auto-detected dotnet MSBuild at: ${msBuildPath}")
119-
}
120-
}
121-
}
122-
123-
// Use MSBuildPath if available (takes precedence over MSBuildVersion)
124-
if (msBuildPath) {
125-
args '-MSBuildPath', msBuildPath
126-
project.logger.debug("Using MSBuildPath: ${msBuildPath}")
127-
} else {
128-
// Skip MSBuildVersion on non-Windows if no MSBuildPath found
129-
// because Mono's xbuild/MSBuild doesn't work properly with NuGet restore
130-
project.logger.debug("Skipping MSBuildVersion on non-Windows platform (no dotnet MSBuild found)")
131-
}
115+
// Skip MSBuildPath on non-Windows to avoid assembly loading errors
116+
// If useDotnetRestore=true, BaseNuGet will use 'dotnet restore' which handles MSBuild automatically
117+
project.logger.debug("Skipping MSBuildPath on non-Windows platform (NuGet will use Mono's xbuild/MSBuild)")
132118
} else {
133119
// On Windows, use MSBuildVersion as before
134120
if (!msBuildVersion) msBuildVersion = GradleHelper.getPropertyFromTask(project, 'version', 'msbuild')
135121
if (msBuildVersion) args '-MsBuildVersion', msBuildVersion
136-
}
137-
138-
// MSBuildPath can also be explicitly set on Windows
139-
if (msBuildPath) {
140-
args '-MSBuildPath', msBuildPath
122+
123+
// MSBuildPath can also be explicitly set on Windows
124+
if (msBuildPath) {
125+
args '-MSBuildPath', msBuildPath
126+
}
141127
}
142128

143129
project.logger.info "Restoring NuGet packages " +
@@ -162,22 +148,6 @@ class NuGetRestore extends BaseNuGet {
162148
return new File(solutionDir ? solutionDir.toString() : '.', 'packages')
163149
}
164150

165-
/**
166-
* Find the dotnet executable path
167-
*/
168-
private String findDotnetPath() {
169-
try {
170-
def process = ['which', 'dotnet'].execute()
171-
process.waitFor()
172-
if (process.exitValue() == 0) {
173-
return process.text.trim()
174-
}
175-
} catch (Exception e) {
176-
// dotnet not found
177-
}
178-
return null
179-
}
180-
181151
/**
182152
* Find the MSBuild.dll path in the dotnet SDK
183153
*/

0 commit comments

Comments
 (0)