@@ -117,54 +117,118 @@ class BaseNuGet extends Exec {
117117 // This ensures solution files, packages.config, etc. are added to args
118118 // The dummy exec() method above allows super.exec() calls to succeed
119119
120- // Call the subclass's exec() method using metaClass to bypass BaseNuGet.exec()
121- // This ensures we call the actual overridden method in the subclass
120+ // Get args BEFORE calling subclass exec() to see what's already there
121+ def argsBeforeExec = getArgs(). toList()
122+ project. logger. info(" Args BEFORE calling subclass exec(): ${ argsBeforeExec} " )
123+
124+ // Check if solutionFile is set before calling exec()
122125 try {
123- def execMethod = this . class . declaredMethods . find { it . name == ' exec ' && it . declaringClass != Exec . class }
124- if (execMethod != null ) {
125- // Use metaClass to invoke the actual subclass method, not BaseNuGet.exec()
126- this . metaClass . invokeMethod( this , ' exec ' , null )
126+ if ( this . hasProperty( ' solutionFile ' )) {
127+ project . logger . info( " solutionFile property exists: ${ this.solutionFile } " )
128+ } else {
129+ project . logger . warn( " solutionFile property does NOT exist on task " )
127130 }
128131 } catch (Exception e) {
129- // Subclass exec() call failed, continue with execution
132+ project . logger . debug( " Could not check solutionFile property: ${ e.message } " )
130133 }
131134
132- // Manually add solution file or packages.config if this is a NuGetRestore task
133- // This is a workaround because args set in subclass exec() might not be preserved
135+ // Call the subclass's exec() method using metaClass to bypass BaseNuGet.exec()
136+ // This ensures we call the actual overridden method in the subclass
134137 try {
135- if (this . hasProperty(' solutionFile' )) {
136- def solutionFile = this . solutionFile
137- if (solutionFile) {
138- args project. file(solutionFile)
139- }
138+ // Check if this class (or any parent up to BaseNuGet) has an exec() method
139+ def execMethod = null
140+ def currentClass = this . class
141+ while (currentClass != null && currentClass != BaseNuGet . class && currentClass != Exec . class) {
142+ execMethod = currentClass. declaredMethods. find { it. name == ' exec' && it. parameterCount == 0 }
143+ if (execMethod != null ) break
144+ currentClass = currentClass. superclass
140145 }
141- if (this . hasProperty(' packagesConfigFile' )) {
142- def packagesConfigFile = this . packagesConfigFile
143- if (packagesConfigFile) {
144- args project. file(packagesConfigFile)
145- }
146+
147+ if (execMethod != null ) {
148+ project. logger. info(" Calling subclass exec() method: ${ execMethod.declaringClass.simpleName} .exec()" )
149+ // Directly invoke the method using reflection to bypass Gradle's method interception
150+ execMethod. invoke(this )
151+ } else {
152+ project. logger. warn(" No exec() method found in subclass ${ this.class.simpleName} " )
146153 }
147154 } catch (Exception e) {
148- // Could not add solution/packages file, continue with execution
155+ // Subclass exec() call failed, continue with execution
156+ project. logger. warn(" Could not call subclass exec(): ${ e.class.simpleName} : ${ e.message} " )
157+ e. printStackTrace()
149158 }
150159
160+ // Get args AFTER calling subclass exec() to see what was added
161+ def argsAfterExec = getArgs(). toList()
162+ project. logger. info(" Args AFTER calling subclass exec(): ${ argsAfterExec} " )
163+
151164 // Now configure executable and final args
152165 File localNuget = getNugetExeLocalPath()
153166 project. logger. debug " Using NuGet from path $localNuget . path "
154167
155- // Get current args (should include solution file, packages.config, etc.)
168+ // Get current args (should include solution file, packages.config, etc. from subclass exec() )
156169 def currentArgs = getArgs(). toList()
170+ project. logger. info(" Args before deduplication: ${ currentArgs} " )
171+
172+ // Build a set of expected file paths to ensure we only add them once
173+ def expectedPaths = new HashSet ()
174+ try {
175+ if (this . hasProperty(' solutionFile' ) && this . solutionFile) {
176+ def solutionFilePath = this . solutionFile instanceof File ? this . solutionFile. absolutePath : project. file(this . solutionFile). absolutePath
177+ expectedPaths. add(solutionFilePath. replace(' /' , File . separator). toLowerCase())
178+ }
179+ if (this . hasProperty(' packagesConfigFile' ) && this . packagesConfigFile) {
180+ def packagesConfigFilePath = this . packagesConfigFile instanceof File ? this . packagesConfigFile. absolutePath : project. file(this . packagesConfigFile). absolutePath
181+ expectedPaths. add(packagesConfigFilePath. replace(' /' , File . separator). toLowerCase())
182+ }
183+ } catch (Exception e) {
184+ project. logger. debug(" Could not determine expected paths: ${ e.message} " )
185+ }
186+
187+ // Remove duplicates from args - normalize paths and check for duplicates
188+ // This is critical because the solution file might be added multiple times
189+ def normalizedArgs = []
190+ def seenPaths = new HashSet ()
191+ currentArgs. each { arg ->
192+ def normalizedPath = arg instanceof File ? arg. absolutePath. replace(' /' , File . separator) : arg. toString(). replace(' /' , File . separator)
193+ def normalizedPathLower = normalizedPath. toLowerCase()
194+
195+ // Skip if we've seen this exact path before
196+ if (! seenPaths. contains(normalizedPathLower)) {
197+ seenPaths. add(normalizedPathLower)
198+ // Convert File objects to strings for consistent argument handling
199+ normalizedArgs. add(arg instanceof File ? normalizedPath : arg)
200+ } else {
201+ project. logger. info(" Skipping duplicate arg: ${ normalizedPath} " )
202+ }
203+ }
204+
205+ // DO NOT manually add solution file or packages.config here
206+ // The subclass exec() method should have already added them via args
207+ // Adding them here would cause duplicates
208+ // The deduplication above should handle any duplicates that were added
209+
210+ project. logger. info(" Args after deduplication: ${ normalizedArgs} " )
211+
212+ // Clear all args first to avoid any accumulation issues
213+ setArgs([])
214+
215+ // Set deduplicated args
216+ setArgs(normalizedArgs)
157217
158218 if (isFamily(FAMILY_WINDOWS )) {
159219 executable = localNuget. absolutePath
160220 } else {
161221 executable = " mono"
162222 // Prepend nuget.exe path to args for mono execution
163- setArgs([localNuget. absolutePath] + currentArgs )
223+ setArgs([localNuget. absolutePath] + normalizedArgs )
164224 }
225+
226+ // Add flags after setting the base args
165227 args ' -NonInteractive'
166228 args ' -Verbosity' , (verbosity ?: getNugetVerbosity())
167229
230+ project. logger. info(" Final args before execution: ${ getArgs().toList()} " )
231+
168232 // Check if we should ignore failures on non-Windows (for Mono xbuild issues)
169233 // Set ignoreExitValue on the Exec task itself before execution
170234 def shouldIgnoreFailures = false
0 commit comments