From 03feed21614447fe391f28fcebb59198892f402b Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Tue, 14 Apr 2026 16:25:41 +0200 Subject: [PATCH 01/11] Bugfix :: Fix codegen runtime crashes: TypeLoadException, InvalidProgramException, stack corruption (#19338) * Fix runtime crashes: TypeLoadException, InvalidProgramException, stack corruption --- .../.FSharp.Compiler.Service/10.0.300.md | 6 + .../.FSharp.Compiler.Service/11.0.100.md | 6 + src/Compiler/AbstractIL/il.fs | 9 + src/Compiler/AbstractIL/il.fsi | 1 + src/Compiler/CodeGen/EraseClosures.fs | 36 +- src/Compiler/CodeGen/IlxGen.fs | 210 +++++++-- .../CodeGenRegressions_Crashes.fs | 428 ++++++++++++++++++ .../StaticOptimizations/String_Enum.fs.il.bsl | 1 - .../TestFunction03.fs.OptimizeOff.il.bsl | 11 - ...estFunction03.fs.OptimizeOn.il.release.bsl | 1 - .../TestFunction03b.fs.OptimizeOff.il.bsl | 11 - ...stFunction03b.fs.OptimizeOn.il.release.bsl | 1 - .../TestFunction03c.fs.OptimizeOff.il.bsl | 13 +- ...stFunction03c.fs.OptimizeOn.il.release.bsl | 1 - .../FSharp.Compiler.ComponentTests.fsproj | 1 + 15 files changed, 645 insertions(+), 91 deletions(-) create mode 100644 tests/FSharp.Compiler.ComponentTests/EmittedIL/CodeGenRegressions/CodeGenRegressions_Crashes.fs diff --git a/docs/release-notes/.FSharp.Compiler.Service/10.0.300.md b/docs/release-notes/.FSharp.Compiler.Service/10.0.300.md index 7a8279aac29..53802cc1153 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/10.0.300.md +++ b/docs/release-notes/.FSharp.Compiler.Service/10.0.300.md @@ -25,6 +25,12 @@ * F# Scripts: Fix default reference paths resolving when an SDK directory is specified. ([PR #19270](https://github.com/dotnet/fsharp/pull/19270)) * Improve static compilation of state machines. ([PR #19297](https://github.com/dotnet/fsharp/pull/19297)) * Fix a bug where `let!` and `use!` were incorrectly allowed outside computation expressions. [PR #19347](https://github.com/dotnet/fsharp/pull/19347) +* Fix TypeLoadException when creating delegate with voidptr parameter. (Issue [#11132](https://github.com/dotnet/fsharp/issues/11132), [PR #19338](https://github.com/dotnet/fsharp/pull/19338)) +* Suppress tail calls when localloc (NativePtr.stackalloc) is used. (Issue [#13447](https://github.com/dotnet/fsharp/issues/13447), [PR #19338](https://github.com/dotnet/fsharp/pull/19338)) +* Fix TypeLoadException in Release builds with inline constraints. (Issue [#14492](https://github.com/dotnet/fsharp/issues/14492), [PR #19338](https://github.com/dotnet/fsharp/pull/19338)) +* Fix nativeptr in interfaces leads to TypeLoadException. (Issue [#14508](https://github.com/dotnet/fsharp/issues/14508), [PR #19338](https://github.com/dotnet/fsharp/pull/19338)) +* Fix box instruction for literal upcasts. (Issue [#18319](https://github.com/dotnet/fsharp/issues/18319), [PR #19338](https://github.com/dotnet/fsharp/pull/19338)) +* Fix Decimal Literal causes InvalidProgramException in Debug builds. (Issue [#18956](https://github.com/dotnet/fsharp/issues/18956), [PR #19338](https://github.com/dotnet/fsharp/pull/19338)) ### Added * FSharpType: add ImportILType ([PR #19300](https://github.com/dotnet/fsharp/pull/19300)) diff --git a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md index d2d59c319a1..7d2823606ed 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md +++ b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md @@ -19,6 +19,12 @@ * Fixed how the source ranges of warn directives are reported (as trivia) in the parser output (by not reporting leading spaces). ([Issue #19405](https://github.com/dotnet/fsharp/issues/19405), [PR #19408]((https://github.com/dotnet/fsharp/pull/19408))) * Fix UoM value type `ToString()` returning garbage values when `--checknulls+` is enabled, caused by double address-taking in codegen. ([Issue #19435](https://github.com/dotnet/fsharp/issues/19435), [PR #19440](https://github.com/dotnet/fsharp/pull/19440)) * Fix completion inconsistently showing some obsolete members (fields and events) while hiding others (methods and properties). All obsolete members are now consistently hidden by default. ([Issue #13512](https://github.com/dotnet/fsharp/issues/13512), [PR #19506](https://github.com/dotnet/fsharp/pull/19506)) +* Fix TypeLoadException when creating delegate with voidptr parameter. (Issue [#11132](https://github.com/dotnet/fsharp/issues/11132), [PR #19338](https://github.com/dotnet/fsharp/pull/19338)) +* Suppress tail calls when localloc (NativePtr.stackalloc) is used. (Issue [#13447](https://github.com/dotnet/fsharp/issues/13447), [PR #19338](https://github.com/dotnet/fsharp/pull/19338)) +* Fix TypeLoadException in Release builds with inline constraints. (Issue [#14492](https://github.com/dotnet/fsharp/issues/14492), [PR #19338](https://github.com/dotnet/fsharp/pull/19338)) +* Fix nativeptr in interfaces leads to TypeLoadException. (Issue [#14508](https://github.com/dotnet/fsharp/issues/14508), [PR #19338](https://github.com/dotnet/fsharp/pull/19338)) +* Fix box instruction for literal upcasts. (Issue [#18319](https://github.com/dotnet/fsharp/issues/18319), [PR #19338](https://github.com/dotnet/fsharp/pull/19338)) +* Fix Decimal Literal causes InvalidProgramException in Debug builds. (Issue [#18956](https://github.com/dotnet/fsharp/issues/18956), [PR #19338](https://github.com/dotnet/fsharp/pull/19338)) ### Added diff --git a/src/Compiler/AbstractIL/il.fs b/src/Compiler/AbstractIL/il.fs index b620ee8e7e5..8e836ef548e 100644 --- a/src/Compiler/AbstractIL/il.fs +++ b/src/Compiler/AbstractIL/il.fs @@ -3375,6 +3375,15 @@ let mkILSimpleTypar nm = MetadataIndex = NoMetadataIdx } +let stripILGenericParamConstraints (gp: ILGenericParameterDef) = + { gp with + Constraints = [] + HasReferenceTypeConstraint = false + HasNotNullableValueTypeConstraint = false + HasDefaultConstructorConstraint = false + HasAllowsRefStruct = false + } + let genericParamOfGenericActual (_ga: ILType) = mkILSimpleTypar "T" let mkILFormalTypars (x: ILGenericArgsList) = List.map genericParamOfGenericActual x diff --git a/src/Compiler/AbstractIL/il.fsi b/src/Compiler/AbstractIL/il.fsi index 2071b8658e4..3539e235518 100644 --- a/src/Compiler/AbstractIL/il.fsi +++ b/src/Compiler/AbstractIL/il.fsi @@ -2096,6 +2096,7 @@ val internal mkILFormalNamedTy: ILBoxity -> ILTypeRef -> ILGenericParameterDef l val internal mkILFormalTypars: ILType list -> ILGenericParameterDefs val internal mkILFormalGenericArgs: int -> ILGenericParameterDefs -> ILGenericArgsList val internal mkILSimpleTypar: string -> ILGenericParameterDef +val internal stripILGenericParamConstraints: ILGenericParameterDef -> ILGenericParameterDef /// Make custom attributes. val internal mkILCustomAttribMethRef: diff --git a/src/Compiler/CodeGen/EraseClosures.fs b/src/Compiler/CodeGen/EraseClosures.fs index 930e6201bf8..6aef85d40b5 100644 --- a/src/Compiler/CodeGen/EraseClosures.fs +++ b/src/Compiler/CodeGen/EraseClosures.fs @@ -153,7 +153,19 @@ let newIlxPubCloEnv (ilg, addMethodGeneratedAttrs, addFieldGeneratedAttrs, addFi let mkILTyFuncTy cenv = cenv.mkILTyFuncTy +let inline private (|IsVoidPtr|_|) ty = + match ty with + | ILType.Ptr ILType.Void -> true + | _ -> false + +let private fixVoidPtrForGenericArg (ilg: ILGlobals) ty = + match ty with + | IsVoidPtr -> ilg.typ_IntPtr + | _ -> ty + let mkILFuncTy cenv dty rty = + let dty = fixVoidPtrForGenericArg cenv.ilg dty + let rty = fixVoidPtrForGenericArg cenv.ilg rty mkILBoxedTy cenv.tref_Func[0] [ dty; rty ] let mkILCurriedFuncTy cenv dtys rty = @@ -168,6 +180,8 @@ let typ_Func cenv (dtys: ILType list) rty = else mkFuncTypeRef cenv.ilg.fsharpCoreAssemblyScopeRef n + let dtys = dtys |> List.map (fixVoidPtrForGenericArg cenv.ilg) + let rty = fixVoidPtrForGenericArg cenv.ilg rty mkILBoxedTy tref (dtys @ [ rty ]) let rec mkTyOfApps cenv apps = @@ -190,6 +204,8 @@ let mkMethSpecForMultiApp cenv (argTys: ILType list, retTy) = let n = argTys.Length let formalArgTys = List.mapi (fun i _ -> ILType.TypeVar(uint16 i)) argTys let formalRetTy = ILType.TypeVar(uint16 n) + let argTys = argTys |> List.map (fixVoidPtrForGenericArg cenv.ilg) + let retTy = fixVoidPtrForGenericArg cenv.ilg retTy let inst = argTys @ [ retTy ] if n = 1 then @@ -548,12 +564,14 @@ let rec convIlxClosureDef cenv encl (td: ILTypeDef) clo = let convil = convILMethodBody (Some nowCloSpec, boxReturnTy) clo.cloCode.Value + let specializeGenParams = addedGenParams |> List.map stripILGenericParamConstraints + let nowApplyMethDef = mkILGenericVirtualMethod ( "Specialize", ILCallingConv.Instance, ILMemberAccess.Public, - addedGenParams (* method is generic over added ILGenericParameterDefs *) , + specializeGenParams, [], mkILReturn cenv.ilg.typ_Object, MethodBody.IL(notlazy convil) @@ -681,7 +699,17 @@ let rec convIlxClosureDef cenv encl (td: ILTypeDef) clo = else // CASE 2b - Build an Invoke method - let nowEnvParentClass = typ_Func cenv (typesOfILParams nowParams) nowReturnTy + let fixedNowParams = + nowParams + |> List.map (fun (p: ILParameter) -> + { p with + Type = fixVoidPtrForGenericArg cenv.ilg p.Type + }) + + let fixedNowReturnTy = fixVoidPtrForGenericArg cenv.ilg nowReturnTy + + let nowEnvParentClass = + typ_Func cenv (typesOfILParams fixedNowParams) fixedNowReturnTy let cloTypeDef = let convil = convILMethodBody (Some nowCloSpec, None) clo.cloCode.Value @@ -690,8 +718,8 @@ let rec convIlxClosureDef cenv encl (td: ILTypeDef) clo = mkILNonGenericVirtualInstanceMethod ( "Invoke", ILMemberAccess.Public, - nowParams, - mkILReturn nowReturnTy, + fixedNowParams, + mkILReturn fixedNowReturnTy, MethodBody.IL(notlazy convil) ) diff --git a/src/Compiler/CodeGen/IlxGen.fs b/src/Compiler/CodeGen/IlxGen.fs index 6dcb2f92ee8..b76cc30293f 100644 --- a/src/Compiler/CodeGen/IlxGen.fs +++ b/src/Compiler/CodeGen/IlxGen.fs @@ -1398,6 +1398,41 @@ let TryStorageForWitness (_g: TcGlobals) eenv (w: TraitWitnessInfo) = let IsValRefIsDllImport g (vref: ValRef) = ValHasWellKnownAttribute g WellKnownValAttributes.DllImportAttribute vref.Deref +/// Check if a type contains nativeptr with a type parameter from the given set. +/// Used to detect interface implementations that need 'native int' instead of 'T*'. +let hasNativePtrWithTypar (g: TcGlobals) (typars: Typar list) ty = + let rec check ty = + let ty = stripTyEqns g ty + + match ty with + | TType_app(tcref, tinst, _) when tyconRefEq g g.nativeptr_tcr tcref -> + tinst + |> List.exists (fun t -> + match stripTyEqns g t with + | TType_var(tp, _) -> typars |> List.exists (fun tp2 -> tp.Stamp = tp2.Stamp) + | _ -> false) + | TType_app(_, tinst, _) -> tinst |> List.exists check + | TType_fun(d, r, _) -> check d || check r + | TType_tuple(_, tys) -> tys |> List.exists check + | TType_anon(_, tys) -> tys |> List.exists check + | TType_forall(_, t) -> check t + | _ -> false + + check ty + +/// Check if a slot signature requires nativeptr rewriting: the slot has nativeptr<'T> +/// where 'T is a class type parameter and the interface type arguments are concrete. +/// When true, method definitions must use 'native int' instead of 'T*' for the +/// affected params/returns to match the interface slot's IL signature. +let slotSigRequiresNativePtrRewrite (g: TcGlobals) ty (sctps: Typars) (sParams: SlotParam list list) (sRetTy: TType option) = + not sctps.IsEmpty + && (let interfaceTypeArgs = argsOfAppTy g ty + (freeInTypes CollectTypars interfaceTypeArgs).FreeTypars.IsEmpty) + && (sParams + |> List.concat + |> List.exists (fun (TSlotParam(_, paramTy, _, _, _, _)) -> hasNativePtrWithTypar g sctps paramTy) + || sRetTy |> Option.exists (hasNativePtrWithTypar g sctps)) + /// Determine how a top level value is represented, when it is being represented /// as a method. let GetMethodSpecForMemberVal cenv (memberInfo: ValMemberInfo) (vref: ValRef) = @@ -1423,15 +1458,40 @@ let GetMethodSpecForMemberVal cenv (memberInfo: ValMemberInfo) (vref: ValRef) = let ctps, mtps = List.splitAt numParentTypars tps let isCompiledAsInstance = ValRefIsCompiledAsInstanceMember g vref + // When implementing an interface slot with nativeptr<'T> where 'T is an interface + // type parameter and the interface type args are concrete, the interface method + // uses 'native int'. The method def must also use 'native int' to match. + // This applies to both return types and parameter types. + let nativePtrSlotRewriteInfo = + if not (isCtor || cctor) && memberInfo.MemberFlags.IsOverrideOrExplicitImpl then + memberInfo.ImplementedSlotSigs + |> List.tryPick (fun (TSlotSig(_, ty, sctps, _, sParams, sRetTy)) -> + if slotSigRequiresNativePtrRewrite g ty sctps sParams sRetTy then + let slotEnv = TypeReprEnv.Empty.ForTypars sctps + let slotParamTys = sParams |> List.concat |> List.map (fun p -> p.Type) + Some(slotEnv, sctps, slotParamTys, sRetTy) + else + None) + else + None + let ilActualRetTy = let ilRetTy = GenReturnType cenv m tyenvUnderTypars returnTy - if isCtor || cctor then ILType.Void else ilRetTy + + if isCtor || cctor then + ILType.Void + else + match nativePtrSlotRewriteInfo with + | Some(slotEnv, sctps, _, sRetTy) when sRetTy |> Option.exists (hasNativePtrWithTypar g sctps) -> + GenReturnType cenv m slotEnv sRetTy + | _ -> ilRetTy let ilTy = GenType cenv m tyenvUnderTypars (mkWoNullAppTy parentTcref (List.map mkTyparTy ctps)) let nm = vref.CompiledName g.CompilerGlobalState + // Instance methods: validate 'this' type and remove it from the arg list if isCompiledAsInstance || isCtor then // Find the 'this' argument type if any let thisTy, flatArgInfos = @@ -1470,41 +1530,40 @@ let GetMethodSpecForMemberVal cenv (memberInfo: ValMemberInfo) (vref: ValRef) = ) )) + flatArgInfos + else + flatArgInfos + // Common: generate param types, method spec, witness spec + |> fun flatArgInfos -> + let methodArgTys, paramInfos = List.unzip flatArgInfos let isSlotSig = memberInfo.MemberFlags.IsDispatchSlot || memberInfo.MemberFlags.IsOverrideOrExplicitImpl - let ilMethodArgTys = GenParamTypes cenv m tyenvUnderTypars isSlotSig methodArgTys - let ilMethodInst = GenTypeArgs cenv m tyenvUnderTypars (List.map mkTyparTy mtps) - - let mspec = - mkILInstanceMethSpecInTy (ilTy, nm, ilMethodArgTys, ilActualRetTy, ilMethodInst) - - let mspecW = - if not g.generateWitnesses || witnessInfos.IsEmpty then - mspec - else - let ilWitnessArgTys = - GenTypes cenv m tyenvUnderTypars (GenWitnessTys g witnessInfos) - - let nmW = ExtraWitnessMethodName nm - mkILInstanceMethSpecInTy (ilTy, nmW, ilWitnessArgTys @ ilMethodArgTys, ilActualRetTy, ilMethodInst) - - mspec, mspecW, ctps, mtps, curriedArgInfos, paramInfos, retInfo, witnessInfos, methodArgTys, returnTy - else - let methodArgTys, paramInfos = List.unzip flatArgInfos + let ilMethodArgTys = + let ilArgTys = GenParamTypes cenv m tyenvUnderTypars isSlotSig methodArgTys - let isSlotSig = - memberInfo.MemberFlags.IsDispatchSlot - || memberInfo.MemberFlags.IsOverrideOrExplicitImpl + match nativePtrSlotRewriteInfo with + | Some(slotEnv, sctps, slotParamTys, _) when slotParamTys.Length = ilArgTys.Length -> + (ilArgTys, slotParamTys) + ||> List.map2 (fun ilArgTy slotParamTy -> + if hasNativePtrWithTypar g sctps slotParamTy then + GenParamType cenv m slotEnv true slotParamTy + else + ilArgTy) + | _ -> ilArgTys - let ilMethodArgTys = GenParamTypes cenv m tyenvUnderTypars isSlotSig methodArgTys let ilMethodInst = GenTypeArgs cenv m tyenvUnderTypars (List.map mkTyparTy mtps) - let mspec = - mkILStaticMethSpecInTy (ilTy, nm, ilMethodArgTys, ilActualRetTy, ilMethodInst) + let mkMethSpec = + if isCompiledAsInstance || isCtor then + mkILInstanceMethSpecInTy + else + mkILStaticMethSpecInTy + + let mspec = mkMethSpec (ilTy, nm, ilMethodArgTys, ilActualRetTy, ilMethodInst) let mspecW = if not g.generateWitnesses || witnessInfos.IsEmpty then @@ -1514,7 +1573,7 @@ let GetMethodSpecForMemberVal cenv (memberInfo: ValMemberInfo) (vref: ValRef) = GenTypes cenv m tyenvUnderTypars (GenWitnessTys g witnessInfos) let nmW = ExtraWitnessMethodName nm - mkILStaticMethSpecInTy (ilTy, nmW, ilWitnessArgTys @ ilMethodArgTys, ilActualRetTy, ilMethodInst) + mkMethSpec (ilTy, nmW, ilWitnessArgTys @ ilMethodArgTys, ilActualRetTy, ilMethodInst) mspec, mspecW, ctps, mtps, curriedArgInfos, paramInfos, retInfo, witnessInfos, methodArgTys, returnTy @@ -2516,6 +2575,8 @@ type CodeGenBuffer(m: range, mgbuf: AssemblyBuilder, methodName, alreadyUsedArgs let mutable hasDebugPoints = false let mutable anyDocument = None // we collect an arbitrary document in order to emit the header FeeFee if needed + let mutable hasStackAllocatedLocals = false + let codeLabelToPC: Dictionary = Dictionary<_, _>(10) let codeLabelToCodeLabel: Dictionary = @@ -2574,11 +2635,19 @@ type CodeGenBuffer(m: range, mgbuf: AssemblyBuilder, methodName, alreadyUsedArgs member cgbuf.EmitInstr(pops, pushes, i) = cgbuf.DoPops pops cgbuf.DoPushes pushes + + if i = I_localloc then + hasStackAllocatedLocals <- true + codebuf.Add i member cgbuf.EmitInstrs(pops, pushes, is) = cgbuf.DoPops pops cgbuf.DoPushes pushes + + if is |> List.exists (fun i -> i = I_localloc) then + hasStackAllocatedLocals <- true + is |> List.iter codebuf.Add member private _.EnsureNopBetweenDebugPoints() = @@ -2711,6 +2780,8 @@ type CodeGenBuffer(m: range, mgbuf: AssemblyBuilder, methodName, alreadyUsedArgs member _.HasPinnedLocals() = locals |> Seq.exists (fun (_, _, isFixed, _) -> isFixed) + member _.HasStackAllocatedLocals() = hasStackAllocatedLocals + member _.Close() = let instrs = codebuf.ToArray() @@ -3341,32 +3412,50 @@ and GenConstant cenv cgbuf eenv (c, m, ty) sequel = match TryEliminateDesugaredConstants g m c with | Some e -> GenExpr cenv cgbuf eenv e Continue | None -> - let emitInt64Constant i = + let needsBoxingToTargetTy = + (match ilTy with + | ILType.Value _ -> false + | _ -> true) + + // Wraps an emitter: calls it, then boxes if target type is not a value type (e.g. literal upcast to obj). + let inline emitAndBoxIfNeeded emitter uty arg = + emitter uty arg + + if needsBoxingToTargetTy then + CG.EmitInstr cgbuf (pop 1) (Push [ ilTy ]) (I_box uty) + + let emitInt64Constant uty i = // see https://github.com/dotnet/fsharp/pull/3620 // and https://github.com/dotnet/fsharp/issue/8683 // and https://github.com/dotnet/roslyn/blob/98f12bb/src/Compilers/Core/Portable/CodeGen/ILBuilderEmit.cs#L679 if i >= int64 Int32.MinValue && i <= int64 Int32.MaxValue then - CG.EmitInstrs cgbuf (pop 0) (Push [ ilTy ]) [ mkLdcInt32 (int32 i); AI_conv DT_I8 ] + CG.EmitInstrs cgbuf (pop 0) (Push [ uty ]) [ mkLdcInt32 (int32 i); AI_conv DT_I8 ] elif i >= int64 UInt32.MinValue && i <= int64 UInt32.MaxValue then - CG.EmitInstrs cgbuf (pop 0) (Push [ ilTy ]) [ mkLdcInt32 (int32 i); AI_conv DT_U8 ] + CG.EmitInstrs cgbuf (pop 0) (Push [ uty ]) [ mkLdcInt32 (int32 i); AI_conv DT_U8 ] else - CG.EmitInstr cgbuf (pop 0) (Push [ ilTy ]) (iLdcInt64 i) + CG.EmitInstr cgbuf (pop 0) (Push [ uty ]) (iLdcInt64 i) + + let emitConst uty instr = + CG.EmitInstr cgbuf (pop 0) (Push [ uty ]) instr + + let emitConstI uty instrs = + CG.EmitInstrs cgbuf (pop 0) (Push [ uty ]) instrs match c with - | Const.Bool b -> CG.EmitInstr cgbuf (pop 0) (Push [ g.ilg.typ_Bool ]) (mkLdcInt32 (if b then 1 else 0)) - | Const.SByte i -> CG.EmitInstr cgbuf (pop 0) (Push [ ilTy ]) (mkLdcInt32 (int32 i)) - | Const.Int16 i -> CG.EmitInstr cgbuf (pop 0) (Push [ ilTy ]) (mkLdcInt32 (int32 i)) - | Const.Int32 i -> CG.EmitInstr cgbuf (pop 0) (Push [ ilTy ]) (mkLdcInt32 i) - | Const.Int64 i -> emitInt64Constant i - | Const.IntPtr i -> CG.EmitInstrs cgbuf (pop 0) (Push [ ilTy ]) [ iLdcInt64 i; AI_conv DT_I ] - | Const.Byte i -> CG.EmitInstr cgbuf (pop 0) (Push [ ilTy ]) (mkLdcInt32 (int32 i)) - | Const.UInt16 i -> CG.EmitInstr cgbuf (pop 0) (Push [ ilTy ]) (mkLdcInt32 (int32 i)) - | Const.UInt32 i -> CG.EmitInstr cgbuf (pop 0) (Push [ ilTy ]) (mkLdcInt32 (int32 i)) - | Const.UInt64 i -> emitInt64Constant (int64 i) - | Const.UIntPtr i -> CG.EmitInstrs cgbuf (pop 0) (Push [ ilTy ]) [ iLdcInt64 (int64 i); AI_conv DT_U ] - | Const.Double f -> CG.EmitInstr cgbuf (pop 0) (Push [ ilTy ]) (AI_ldc(DT_R8, ILConst.R8 f)) - | Const.Single f -> CG.EmitInstr cgbuf (pop 0) (Push [ ilTy ]) (AI_ldc(DT_R4, ILConst.R4 f)) - | Const.Char c -> CG.EmitInstr cgbuf (pop 0) (Push [ ilTy ]) (mkLdcInt32 (int c)) + | Const.Bool b -> emitAndBoxIfNeeded emitConst g.ilg.typ_Bool (mkLdcInt32 (if b then 1 else 0)) + | Const.SByte i -> emitAndBoxIfNeeded emitConst g.ilg.typ_SByte (mkLdcInt32 (int32 i)) + | Const.Int16 i -> emitAndBoxIfNeeded emitConst g.ilg.typ_Int16 (mkLdcInt32 (int32 i)) + | Const.Int32 i -> emitAndBoxIfNeeded emitConst g.ilg.typ_Int32 (mkLdcInt32 i) + | Const.Int64 i -> emitAndBoxIfNeeded emitInt64Constant g.ilg.typ_Int64 i + | Const.IntPtr i -> emitAndBoxIfNeeded emitConstI g.ilg.typ_IntPtr [ iLdcInt64 i; AI_conv DT_I ] + | Const.Byte i -> emitAndBoxIfNeeded emitConst g.ilg.typ_Byte (mkLdcInt32 (int32 i)) + | Const.UInt16 i -> emitAndBoxIfNeeded emitConst g.ilg.typ_UInt16 (mkLdcInt32 (int32 i)) + | Const.UInt32 i -> emitAndBoxIfNeeded emitConst g.ilg.typ_UInt32 (mkLdcInt32 (int32 i)) + | Const.UInt64 i -> emitAndBoxIfNeeded emitInt64Constant g.ilg.typ_UInt64 (int64 i) + | Const.UIntPtr i -> emitAndBoxIfNeeded emitConstI g.ilg.typ_UIntPtr [ iLdcInt64 (int64 i); AI_conv DT_U ] + | Const.Double f -> emitAndBoxIfNeeded emitConst g.ilg.typ_Double (AI_ldc(DT_R8, ILConst.R8 f)) + | Const.Single f -> emitAndBoxIfNeeded emitConst g.ilg.typ_Single (AI_ldc(DT_R4, ILConst.R4 f)) + | Const.Char c -> emitAndBoxIfNeeded emitConst g.ilg.typ_Char (mkLdcInt32 (int c)) | Const.String s -> GenString cenv cgbuf s | Const.Unit -> GenUnit cenv eenv m cgbuf | Const.Zero -> GenDefaultValue cenv cgbuf eenv (ty, m) @@ -4559,6 +4648,7 @@ and CanTailcall // Can't tailcall with a .NET 2.0 generic constrained call since it involves a byref // Can't tailcall when there are pinned locals since the stack frame must remain alive let hasPinnedLocals = cgbuf.HasPinnedLocals() + let hasStackAllocatedLocals = cgbuf.HasStackAllocatedLocals() if not hasStructObjArg @@ -4569,6 +4659,7 @@ and CanTailcall && not isSelfInit && not makesNoCriticalTailcalls && not hasPinnedLocals + && not hasStackAllocatedLocals && // We can tailcall even if we need to generate "unit", as long as we're about to throw the value away anyway as par of the return. @@ -5907,14 +5998,35 @@ and GenActualSlotsig methTyparsOfOverridingMethod (methodParams: Val list) = + let g = cenv.g let ilSlotParams = List.concat ilSlotParams let instForSlotSig = - mkTyparInst (ctps @ mtps) (argsOfAppTy cenv.g ty @ generalizeTypars methTyparsOfOverridingMethod) + mkTyparInst (ctps @ mtps) (argsOfAppTy g ty @ generalizeTypars methTyparsOfOverridingMethod) + + let slotHasNativePtrWithCtps = + slotSigRequiresNativePtrRewrite g ty ctps [ ilSlotParams ] ilSlotRetTy + + let eenvForSlotGen = + if slotHasNativePtrWithCtps then + EnvForTypars ctps eenv + else + eenv + + // When the slot has nativeptr with concrete interface type args, don't substitute + // the class type params (ctps) - only substitute method type params (mtps). + // This keeps nativeptr<'T> unsubstituted so it generates 'native int' in IL, + // matching the interface method's signature. Without this, nativeptr<'T> would be + // substituted to nativeptr which generates 'T*', causing a TypeLoadException. + let instForSlotSigGen = + if slotHasNativePtrWithCtps then + mkTyparInst mtps (generalizeTypars methTyparsOfOverridingMethod) + else + instForSlotSig let ilParams = ilSlotParams - |> List.map (instSlotParam instForSlotSig >> GenSlotParam m cenv eenv) + |> List.map (instSlotParam instForSlotSigGen >> GenSlotParam m cenv eenvForSlotGen) // Use the better names if available let ilParams = @@ -5925,7 +6037,7 @@ and GenActualSlotsig ilParams let ilRetTy = - GenReturnType cenv m eenv.tyenv (Option.map (instType instForSlotSig) ilSlotRetTy) + GenReturnType cenv m eenvForSlotGen.tyenv (Option.map (instType instForSlotSigGen) ilSlotRetTy) let iLRet = mkILReturn ilRetTy @@ -5933,7 +6045,7 @@ and GenActualSlotsig match ilSlotRetTy with | None -> iLRet | Some t -> - match GenAdditionalAttributesForTy cenv.g t with + match GenAdditionalAttributesForTy g t with | [] -> iLRet | attrs -> iLRet.WithCustomAttrs(mkILCustomAttrs attrs) diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/CodeGenRegressions/CodeGenRegressions_Crashes.fs b/tests/FSharp.Compiler.ComponentTests/EmittedIL/CodeGenRegressions/CodeGenRegressions_Crashes.fs new file mode 100644 index 00000000000..e49c3a3b211 --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/CodeGenRegressions/CodeGenRegressions_Crashes.fs @@ -0,0 +1,428 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +namespace EmittedIL + +open Xunit +open FSharp.Test +open FSharp.Test.Compiler +open FSharp.Test.Utilities + +module CodeGenRegressions_Crashes = + + let private getActualIL (result: CompilationResult) = + match result with + | CompilationResult.Success s -> + match s.OutputPath with + | Some p -> + let (_, _, actualIL) = ILChecker.verifyILAndReturnActual [] p [ "// dummy" ] + actualIL + | None -> failwith "No output path" + | _ -> failwith "Compilation failed" + + // https://github.com/dotnet/fsharp/issues/18956 + [] + let ``Issue_18956_DecimalConstantInvalidProgram`` () = + let source = """ +module A = + [] + let B = 42m + +[] +let main args = + printfn "%M" A.B + 0 +""" + FSharp source + |> asExe + |> withDebug + |> compile + |> shouldSucceed + |> run + |> shouldSucceed + |> ignore + + // https://github.com/dotnet/fsharp/issues/18319 + [] + let ``Issue_18319_LiteralUpcastMissingBox`` () = + let source = """ +module Test + +[] +let badobj: System.ValueType = 1 + +[] +let main _ = + System.Console.WriteLine(badobj) + 0 +""" + FSharp source + |> asExe + |> compile + |> shouldSucceed + |> run + |> shouldSucceed + |> ignore + + // https://github.com/dotnet/fsharp/issues/18319 + // Second case from issue: literal upcast used as default parameter value + [] + let ``Issue_18319_LiteralUpcastDefaultParam`` () = + let source = """ +module Test + +open System.Runtime.InteropServices + +[] +let lit: System.ValueType = 1 + +type C() = + static member M([] param: System.ValueType) = + System.Console.WriteLine(param) + +[] +let main _ = + C.M() + C.M(42) + 0 +""" + FSharp source + |> asExe + |> compile + |> shouldSucceed + |> run + |> shouldSucceed + |> ignore + + // https://github.com/dotnet/fsharp/issues/14508 + [] + let ``Issue_14508_NativeptrInInterfaces_CompileOnly`` () = + let source = """ +module Test + +open Microsoft.FSharp.NativeInterop + +type IFoo<'T when 'T : unmanaged> = + abstract member Pointer : nativeptr<'T> + +type Broken() = + member x.Pointer : nativeptr = Unchecked.defaultof<_> + interface IFoo with + member x.Pointer = x.Pointer + +type Working<'T when 'T : unmanaged>() = + member x.Pointer : nativeptr<'T> = Unchecked.defaultof<_> + interface IFoo<'T> with + member x.Pointer = x.Pointer +""" + FSharp source + |> asLibrary + |> compile + |> shouldSucceed + |> ignore + + // https://github.com/dotnet/fsharp/issues/14508 + [] + let ``Issue_14508_NativeptrInInterfaces_RuntimeWorking`` () = + let source = """ +open Microsoft.FSharp.NativeInterop + +type IFoo<'T when 'T : unmanaged> = + abstract member Pointer : nativeptr<'T> + +type Working<'T when 'T : unmanaged>() = + member x.Pointer : nativeptr<'T> = Unchecked.defaultof<_> + interface IFoo<'T> with + member x.Pointer = x.Pointer + +printfn "Working type loaded successfully" +""" + FSharp source + |> asExe + |> compile + |> shouldSucceed + |> run + |> shouldSucceed + |> ignore + + // https://github.com/dotnet/fsharp/issues/14508 + [] + let ``Issue_14508_NativeptrInInterfaces_RuntimeBroken`` () = + let source = """ +open Microsoft.FSharp.NativeInterop + +type IFoo<'T when 'T : unmanaged> = + abstract member Pointer : nativeptr<'T> + +type Broken() = + member x.Pointer : nativeptr = Unchecked.defaultof<_> + interface IFoo with + member x.Pointer = x.Pointer + +let b = Broken() +let p = (b :> IFoo).Pointer +printfn "Broken type loaded and Pointer accessed: %A" p +""" + FSharp source + |> asExe + |> compile + |> shouldSucceed + |> run + |> shouldSucceed + |> ignore + + // https://github.com/dotnet/fsharp/issues/14508 + [] + let ``Issue_14508_NativeptrInInterfaceParams`` () = + let source = """ +open Microsoft.FSharp.NativeInterop + +type IBar<'T when 'T : unmanaged> = + abstract member DoSomething : nativeptr<'T> -> unit + +type BarImpl() = + interface IBar with + member _.DoSomething(_p: nativeptr) = () + +let b = BarImpl() +(b :> IBar).DoSomething(Unchecked.defaultof<_>) +printfn "BarImpl loaded and DoSomething called" +""" + FSharp source + |> asExe + |> compile + |> shouldSucceed + |> run + |> shouldSucceed + |> ignore + + // https://github.com/dotnet/fsharp/issues/14508 + [] + let ``Issue_14508_NativeptrInInterfaceParamsAndReturn`` () = + let source = """ +open Microsoft.FSharp.NativeInterop + +type ITransform<'T when 'T : unmanaged> = + abstract member Transform : nativeptr<'T> -> nativeptr<'T> + +type TransformImpl() = + interface ITransform with + member _.Transform(p: nativeptr) : nativeptr = p + +let t = TransformImpl() +let result = (t :> ITransform).Transform(Unchecked.defaultof<_>) +printfn "TransformImpl loaded, result: %A" result +""" + FSharp source + |> asExe + |> compile + |> shouldSucceed + |> run + |> shouldSucceed + |> ignore + + // https://github.com/dotnet/fsharp/issues/14508 + [] + let ``Issue_14508_NativeptrInInterfaceParamWithOtherParams`` () = + let source = """ +open Microsoft.FSharp.NativeInterop + +type IProcess<'T when 'T : unmanaged> = + abstract member Process : nativeptr<'T> * int -> unit + +type ProcessImpl() = + interface IProcess with + member _.Process((_p: nativeptr), (_len: int)) = () + +let p = ProcessImpl() +(p :> IProcess).Process(Unchecked.defaultof<_>, 42) +printfn "ProcessImpl loaded and Process called" +""" + FSharp source + |> asExe + |> compile + |> shouldSucceed + |> run + |> shouldSucceed + |> ignore + + // https://github.com/dotnet/fsharp/issues/14508 + [] + let ``Issue_14508_NativeptrMultipleParams`` () = + let source = """ +open Microsoft.FSharp.NativeInterop + +type ICopy<'T when 'T : unmanaged> = + abstract member Copy : nativeptr<'T> * nativeptr<'T> * int -> unit + +type CopyImpl() = + interface ICopy with + member _.Copy((_src: nativeptr), (_dst: nativeptr), (_len: int)) = () + +let c = CopyImpl() +(c :> ICopy).Copy(Unchecked.defaultof<_>, Unchecked.defaultof<_>, 10) +printfn "CopyImpl loaded and Copy called" +""" + FSharp source + |> asExe + |> compile + |> shouldSucceed + |> run + |> shouldSucceed + |> ignore + + // https://github.com/dotnet/fsharp/issues/14492 + // Same-assembly variant: constrained generics inlined into closures trigger Specialize + [] + let ``Issue_14492_SameAssemblyInline`` () = + let source = """ +module Test + +#nowarn "3370" + +let inline refEquals<'a when 'a : not struct> (a : 'a) (b : 'a) = obj.ReferenceEquals (a, b) + +let inline tee f x = + f x + x + +let memoizeLatestRef (f: 'a -> 'b) = + let cell = ref None + let f' (x: 'a) = + match !cell with + | Some (x', value) when refEquals x' x -> value + | _ -> f x |> tee (fun y -> cell := Some (x, y)) + f' + +module BugInReleaseConfig = + let test f x = + printfn "%s" (f x) + + let f: string -> string = memoizeLatestRef id + + let run () = test f "ok" + +[] +let main _ = + BugInReleaseConfig.run () + 0 +""" + FSharp source + |> asExe + |> withOptimize + |> compile + |> shouldSucceed + |> run + |> shouldSucceed + |> ignore + + // https://github.com/dotnet/fsharp/issues/13447 + [] + let ``Issue_13447_TailInstructionCorruption`` () = + let source = """ +module Test +open System +open Microsoft.FSharp.NativeInterop + +#nowarn "9" // Uses of this construct may result in the generation of unverifiable .NET IL code + +[] +type MyResult<'T, 'E> = + | Ok of value: 'T + | Error of error: 'E + +let useStackAlloc () : MyResult = + let ptr = NativePtr.stackalloc 100 + let span = Span(NativePtr.toVoidPtr ptr, 100) + span.[0] <- 42uy + Ok (int span.[0]) + +let test () = + match useStackAlloc () with + | Ok v -> v + | Error _ -> -1 +""" + let actualIL = + FSharp source + |> asLibrary + |> withOptimize + |> compile + |> shouldSucceed + |> getActualIL + + let useStackAllocIdx = actualIL.IndexOf("useStackAlloc") + Assert.True(useStackAllocIdx >= 0, "useStackAlloc method not found in IL") + let methodEnd = actualIL.IndexOf("\n } ", useStackAllocIdx) + let methodIL = if methodEnd > 0 then actualIL.Substring(useStackAllocIdx, methodEnd - useStackAllocIdx) else actualIL.Substring(useStackAllocIdx) + Assert.DoesNotContain("tail.", methodIL) + + // https://github.com/dotnet/fsharp/issues/11132 + [] + let ``Issue_11132_VoidptrDelegate`` () = + let source = """ +module Test +#nowarn "9" + +open System + +type MyDelegate = delegate of voidptr -> unit + +let method (ptr: voidptr) = () + +let getDelegate (m: voidptr -> unit) : MyDelegate = MyDelegate(m) + +let test() = + let d = getDelegate method + d.Invoke(IntPtr.Zero.ToPointer()) + +do test() +""" + FSharp source + |> asExe + |> withOptimize + |> compileAndRun + |> shouldSucceed + |> ignore + + // https://github.com/dotnet/fsharp/issues/14492 + // Variant: cross-assembly inlining with constrained generics triggers Specialize closure + [] + let ``Issue_14492_CrossAssemblyInline`` () = + let lib = FSharp """ +module Lib + +let inline refEquals<'a when 'a : not struct> (a : 'a) (b : 'a) = obj.ReferenceEquals (a, b) + +let inline tee f x = + f x + x +""" |> withOptimize + |> asLibrary + + let app = FSharp """ +module App + +open Lib + +let memoizeLatestRef (f: 'a -> 'b) = + let cell = ref None + let f' (x: 'a) = + match cell.Value with + | Some (x', value) when refEquals x' x -> value + | _ -> f x |> tee (fun y -> cell.Value <- Some (x, y)) + f' + +module Test = + let f: string -> string = memoizeLatestRef id + let run () = printfn "%s" (f "ok") + +[] +let main _ = Test.run(); 0 +""" + app + |> withOptimize + |> asExe + |> withReferences [lib] + |> compileAndRun + |> shouldSucceed + |> ignore + diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/StaticOptimizations/String_Enum.fs.il.bsl b/tests/FSharp.Compiler.ComponentTests/EmittedIL/StaticOptimizations/String_Enum.fs.il.bsl index 740505ce6a7..1af36573396 100644 --- a/tests/FSharp.Compiler.ComponentTests/EmittedIL/StaticOptimizations/String_Enum.fs.il.bsl +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/StaticOptimizations/String_Enum.fs.il.bsl @@ -540,4 +540,3 @@ - diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/TestFunctions/TestFunction03.fs.OptimizeOff.il.bsl b/tests/FSharp.Compiler.ComponentTests/EmittedIL/TestFunctions/TestFunction03.fs.OptimizeOff.il.bsl index dd6e6ab1b40..9de983bbfa2 100644 --- a/tests/FSharp.Compiler.ComponentTests/EmittedIL/TestFunctions/TestFunction03.fs.OptimizeOff.il.bsl +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/TestFunctions/TestFunction03.fs.OptimizeOff.il.bsl @@ -16,16 +16,6 @@ .hash algorithm 0x00008004 .ver 0:0:0:0 -} -.mresource public FSharpSignatureCompressedData.assembly -{ - - -} -.mresource public FSharpOptimizationCompressedData.assembly -{ - - } .module assembly.exe @@ -112,4 +102,3 @@ - diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/TestFunctions/TestFunction03.fs.OptimizeOn.il.release.bsl b/tests/FSharp.Compiler.ComponentTests/EmittedIL/TestFunctions/TestFunction03.fs.OptimizeOn.il.release.bsl index d360b2a91eb..aa576cc3d13 100644 --- a/tests/FSharp.Compiler.ComponentTests/EmittedIL/TestFunctions/TestFunction03.fs.OptimizeOn.il.release.bsl +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/TestFunctions/TestFunction03.fs.OptimizeOn.il.release.bsl @@ -123,4 +123,3 @@ - diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/TestFunctions/TestFunction03b.fs.OptimizeOff.il.bsl b/tests/FSharp.Compiler.ComponentTests/EmittedIL/TestFunctions/TestFunction03b.fs.OptimizeOff.il.bsl index 6b0f2cd2642..15905929f58 100644 --- a/tests/FSharp.Compiler.ComponentTests/EmittedIL/TestFunctions/TestFunction03b.fs.OptimizeOff.il.bsl +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/TestFunctions/TestFunction03b.fs.OptimizeOff.il.bsl @@ -16,16 +16,6 @@ .hash algorithm 0x00008004 .ver 0:0:0:0 -} -.mresource public FSharpSignatureCompressedData.assembly -{ - - -} -.mresource public FSharpOptimizationCompressedData.assembly -{ - - } .module assembly.exe @@ -126,4 +116,3 @@ - diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/TestFunctions/TestFunction03b.fs.OptimizeOn.il.release.bsl b/tests/FSharp.Compiler.ComponentTests/EmittedIL/TestFunctions/TestFunction03b.fs.OptimizeOn.il.release.bsl index 0a3ae50e3e0..05caaf30094 100644 --- a/tests/FSharp.Compiler.ComponentTests/EmittedIL/TestFunctions/TestFunction03b.fs.OptimizeOn.il.release.bsl +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/TestFunctions/TestFunction03b.fs.OptimizeOn.il.release.bsl @@ -130,4 +130,3 @@ - diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/TestFunctions/TestFunction03c.fs.OptimizeOff.il.bsl b/tests/FSharp.Compiler.ComponentTests/EmittedIL/TestFunctions/TestFunction03c.fs.OptimizeOff.il.bsl index 0eeedf099d9..f67fcf38d92 100644 --- a/tests/FSharp.Compiler.ComponentTests/EmittedIL/TestFunctions/TestFunction03c.fs.OptimizeOff.il.bsl +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/TestFunctions/TestFunction03c.fs.OptimizeOff.il.bsl @@ -8,7 +8,7 @@ .assembly extern netstandard { .publickeytoken = (CC 7B 13 FF CD 2D DD 51 ) - .ver 2:0:0:0 + .ver 2:1:0:0 } .assembly assembly { @@ -21,16 +21,6 @@ .hash algorithm 0x00008004 .ver 0:0:0:0 -} -.mresource public FSharpSignatureCompressedData.assembly -{ - - -} -.mresource public FSharpOptimizationCompressedData.assembly -{ - - } .module assembly.exe @@ -145,4 +135,3 @@ - diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/TestFunctions/TestFunction03c.fs.OptimizeOn.il.release.bsl b/tests/FSharp.Compiler.ComponentTests/EmittedIL/TestFunctions/TestFunction03c.fs.OptimizeOn.il.release.bsl index ad3903afb89..3dbb67a8837 100644 --- a/tests/FSharp.Compiler.ComponentTests/EmittedIL/TestFunctions/TestFunction03c.fs.OptimizeOn.il.release.bsl +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/TestFunctions/TestFunction03c.fs.OptimizeOn.il.release.bsl @@ -141,4 +141,3 @@ - diff --git a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj index f5d1048408e..5d808376295 100644 --- a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj +++ b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj @@ -283,6 +283,7 @@ + From f325ba255f799afc62d668b1642969bcc071d33f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 14 Apr 2026 15:02:58 +0000 Subject: [PATCH 02/11] Add regression test: #14566, false positive unused value warnings in query CEs (#19581) Fixes https://github.com/dotnet/fsharp/issues/14566 Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Language/ComputationExpressionTests.fs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/FSharp.Compiler.ComponentTests/Language/ComputationExpressionTests.fs b/tests/FSharp.Compiler.ComponentTests/Language/ComputationExpressionTests.fs index 13652e42ef3..9285e588f46 100644 --- a/tests/FSharp.Compiler.ComponentTests/Language/ComputationExpressionTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/Language/ComputationExpressionTests.fs @@ -2270,6 +2270,9 @@ let data2 = [(1, "one"); (2, "two"); (3, "three")] let result = query { for x in data1 do join (y, name) in data2 on (x = y); select name }""" "let result = query { for a in [1;2;3] do for b in [4;5;6] do where (a < b); select (a + b) }" "let result = query { for x in [3;1;2] do sortBy x; select x }" + // https://github.com/dotnet/fsharp/issues/14566 + """let result = query { join a in ["x"] on ("x" = a); join b in ["y"] on ("y" = b); select a }""" + """let result = query { for r in [1;2;3] do for i in [true; false] do where i; select r }""" ] |> List.map (fun s -> [| box s |]) From 6e6833a18bcc23ac01ea0b3740d9efdf3a831806 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 14 Apr 2026 15:43:01 +0000 Subject: [PATCH 03/11] Add regression test: #4473, extern function parameters not flagged as unused (#19580) Fixes https://github.com/dotnet/fsharp/issues/4473 Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../ErrorMessages/WarnExpressionTests.fs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/FSharp.Compiler.ComponentTests/ErrorMessages/WarnExpressionTests.fs b/tests/FSharp.Compiler.ComponentTests/ErrorMessages/WarnExpressionTests.fs index b5cf1bcbccb..bc68eb4d7d9 100644 --- a/tests/FSharp.Compiler.ComponentTests/ErrorMessages/WarnExpressionTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/ErrorMessages/WarnExpressionTests.fs @@ -277,3 +277,19 @@ let main _argv = """ |> typecheck |> shouldSucceed + + // https://github.com/dotnet/fsharp/issues/4473 + [] + let ``Issue 4473 - extern function parameters not flagged as unused with warnon 1182``() = + FSharp """ +module Test4473 + +open System.Runtime.InteropServices + +[] +extern bool Beep(int frequency, int duration) + """ + |> withWarnOn 1182 + |> asLibrary + |> typecheck + |> shouldSucceed From 0b91207e844d831f2be6b39dff72c6e47ec10743 Mon Sep 17 00:00:00 2001 From: Vlad Dyshakov Date: Tue, 14 Apr 2026 22:53:11 +0700 Subject: [PATCH 04/11] Add compiler options parser tests (#13878) (#19554) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ParserErrors.fs — horizontal error handling coverage: missing arguments (error 224), invalid integer arguments (error 241), out-of-range warn level (error 1050), unrecognized target (error 1048), unsupported checksum algorithm (error 1065), unrecognized options (error 243). UncoveredOptions.fs — smoke tests for options without dedicated coverage: switch options (+/-), unit options, valid string values, compilation modes, diagnostic options. Co-authored-by: Tomas Grosup --- .../CompilerOptions/Fsc/ParserErrors.fs | 117 ++++++++++++++++++ .../CompilerOptions/Fsc/UncoveredOptions.fs | 109 ++++++++++++++++ .../FSharp.Compiler.ComponentTests.fsproj | 2 + 3 files changed, 228 insertions(+) create mode 100644 tests/FSharp.Compiler.ComponentTests/CompilerOptions/Fsc/ParserErrors.fs create mode 100644 tests/FSharp.Compiler.ComponentTests/CompilerOptions/Fsc/UncoveredOptions.fs diff --git a/tests/FSharp.Compiler.ComponentTests/CompilerOptions/Fsc/ParserErrors.fs b/tests/FSharp.Compiler.ComponentTests/CompilerOptions/Fsc/ParserErrors.fs new file mode 100644 index 00000000000..15d896680d5 --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/CompilerOptions/Fsc/ParserErrors.fs @@ -0,0 +1,117 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +namespace CompilerOptions.Fsc + +open Xunit +open FSharp.Test.Compiler + +/// Tests for compiler options parser error handling across many options +module ParserErrors = + + // ================================================================= + // Missing argument — options that require a parameter (error 224) + // ================================================================= + + [] + [] + [] + [] + [] + [] + [] + [] + [] + [] + [] + [] + [] + [] + [] + [] + [] + [] + [] + [] + let ``options requiring a parameter produce error 224 when missing`` (option: string) = + Fs """module M""" + |> withOptions [option] + |> compile + |> shouldFail + |> withErrorCode 224 + |> withDiagnosticMessageMatches "Option requires parameter" + |> ignore + + // ================================================================= + // Invalid integer argument — options expecting int get a non-integer + // ================================================================= + + [] + [] + [] + let ``int options with non-integer argument produce error 241`` (option: string) = + Fs """module M""" + |> withOptions [option] + |> compile + |> shouldFail + |> withErrorCode 241 + |> withDiagnosticMessageMatches "is not a valid integer argument" + |> ignore + + // ================================================================= + // Invalid warn level — out of valid range 0-5 + // ================================================================= + + [] + let ``warn with negative level produces error 1050`` () = + Fs """module M""" + |> withOptions ["--warn:-1"] + |> compile + |> shouldFail + |> withErrorCode 1050 + |> withDiagnosticMessageMatches "Invalid warning level" + |> ignore + + // ================================================================= + // Invalid target value + // ================================================================= + + [] + let ``target with unrecognized value produces error 1048`` () = + Fs """module M""" + |> withOptions ["--target:dll"] + |> compile + |> shouldFail + |> withErrorCode 1048 + |> withDiagnosticMessageMatches "Unrecognized target" + |> ignore + + // ================================================================= + // Invalid checksum algorithm + // ================================================================= + + [] + let ``checksumalgorithm with unsupported algorithm produces error 1065`` () = + Fs """module M""" + |> withOptions ["--checksumalgorithm:MD5"] + |> compile + |> shouldFail + |> withErrorCode 1065 + |> withDiagnosticMessageMatches "Algorithm.*is not supported" + |> ignore + + // ================================================================= + // Unrecognized option (error 243) + // ================================================================= + + [] + [] + [] // case-sensitive: --optimize (parsed as --optimize+) works, --Optimize does not + [] + let ``unrecognized options produce error 243`` (option: string) = + Fs """module M""" + |> withOptions [option] + |> compile + |> shouldFail + |> withErrorCode 243 + |> withDiagnosticMessageMatches "Unrecognized option" + |> ignore diff --git a/tests/FSharp.Compiler.ComponentTests/CompilerOptions/Fsc/UncoveredOptions.fs b/tests/FSharp.Compiler.ComponentTests/CompilerOptions/Fsc/UncoveredOptions.fs new file mode 100644 index 00000000000..dc0c6e0762f --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/CompilerOptions/Fsc/UncoveredOptions.fs @@ -0,0 +1,109 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +namespace CompilerOptions.Fsc + +open Xunit +open FSharp.Test.Compiler + +/// Smoke tests for compiler options that have no dedicated test coverage +module UncoveredOptions = + + // ================================================================= + // Switch options (+/-) — verify parser accepts both polarities + // ================================================================= + + [] + [] + [] + [] + [] + [] + [] + [] + [] + [] + [] + [] + [] + [] + [] + let ``switch options are accepted by the parser`` (option: string) = + Fs """module M""" + |> withOptions [option] + |> ignoreWarnings + |> typecheck + |> shouldSucceed + |> ignore + + // ================================================================= + // Unit options (no argument) — verify parser accepts them + // ================================================================= + + [] + [] + [] + [] + [] + [] // typecheck only — compile would write .fsi files + [] + [] + let ``unit options are accepted by the parser`` (option: string) = + Fs """module M""" + |> withOptions [option] + |> ignoreWarnings + |> typecheck + |> shouldSucceed + |> ignore + + // ================================================================= + // String options with valid values — verify parser + compilation + // ================================================================= + + [] + [] + [] + let ``checksumalgorithm with valid algorithm is accepted`` (algorithm: string) = + Fs """module M""" + |> withOptions [$"--checksumalgorithm:{algorithm}"] + |> compile + |> shouldSucceed + |> ignore + + [] + [] + [] + [] + [] + let ``target with valid values is accepted`` (option: string) = + Fs """module M""" + |> withOptions [option] + |> ignoreWarnings + |> compile + |> shouldSucceed + |> ignore + + // ================================================================= + // Compilation modes + // ================================================================= + + [] + let ``parseonly does not report type errors`` () = + Fs """let x: int = "not an int" """ + |> asExe + |> withOptions ["--parseonly"] + |> ignoreWarnings + |> compile + |> shouldSucceed + |> ignore + + // ================================================================= + // Diagnostic format options + // ================================================================= + + [] + let ``maxerrors with valid value is accepted`` () = + Fs """module M""" + |> withOptions ["--maxerrors:10"] + |> compile + |> shouldSucceed + |> ignore diff --git a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj index 5d808376295..a345c31d728 100644 --- a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj +++ b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj @@ -418,6 +418,8 @@ + + From 295146c9803d05f30ca218f76dd389553829c7bb Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Tue, 14 Apr 2026 20:17:54 +0200 Subject: [PATCH 05/11] Add regression test: #13114, SynPat.Record and QuoteExpr traversal (#19468) * Add regression test for #9382: SRTP stress test with matrix inverse Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add regression test for #13114: SynPat.Record and QuoteExpr traversal Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix regression tests for #13114: remove unrelated test, add IsInst and FromParseError - Remove the SRTP matrix inverse test from MemberConstraints.fs: it references issue #9382 and is unrelated to this PR (#13114). - Add SynPat.IsInst test: verifies defaultTraverse descends into the SynType inside :? type-test patterns. - Add SynPat.FromParseError test: verifies defaultTraverse descends into the nested pattern wrapped by FromParseError. Per reviewer feedback from @abonie. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Remove extra trailing newline in TreeVisitorTests (retrigger CI) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Remove SynPat.IsInst test: defaultTraverse does not handle IsInst (bug still present) SynPat.IsInst is not in the traversePat defaultTraverse in ServiceParseTreeWalk.fs, so the test asserting it works was failing on all platforms. Keeping Record, QuoteExpr, and Paren (FromParseError-labelled) tests which pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix #13114: Handle SynPat.IsInst and SynPat.FromParseError in defaultTraverse Add SynPat.IsInst and SynPat.FromParseError cases to the defaultTraverse function in traversePat (ServiceParseTreeWalk.fs): - SynPat.IsInst: traverses into the SynType via traverseSynType - SynPat.FromParseError: traverses into the wrapped SynPat Add regression test for SynPat.IsInst traversal using :? type test pattern. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Rename 'pat' to 'ty' in SynPat.IsInst case (it's a type, not a pattern) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- src/Compiler/Service/ServiceParseTreeWalk.fs | 2 + .../TreeVisitorTests.fs | 108 ++++++++++++++++++ 2 files changed, 110 insertions(+) diff --git a/src/Compiler/Service/ServiceParseTreeWalk.fs b/src/Compiler/Service/ServiceParseTreeWalk.fs index 51e2b8ac43b..4a1177b7b26 100644 --- a/src/Compiler/Service/ServiceParseTreeWalk.fs +++ b/src/Compiler/Service/ServiceParseTreeWalk.fs @@ -801,6 +801,8 @@ module SyntaxTraversal = | None -> traverseSynType path ty | x -> x | SynPat.QuoteExpr(expr, _) -> traverseSynExpr path expr + | SynPat.IsInst(ty, _) -> traverseSynType path ty + | SynPat.FromParseError(p, _) -> traversePat path p | _ -> None visitor.VisitPat(origPath, defaultTraverse, pat) diff --git a/tests/FSharp.Compiler.Service.Tests/TreeVisitorTests.fs b/tests/FSharp.Compiler.Service.Tests/TreeVisitorTests.fs index 2895a53210c..7d8b10502d3 100644 --- a/tests/FSharp.Compiler.Service.Tests/TreeVisitorTests.fs +++ b/tests/FSharp.Compiler.Service.Tests/TreeVisitorTests.fs @@ -167,3 +167,111 @@ type Meh = match SyntaxTraversal.Traverse(pos, parseTree, visitor) with | Some "Foo" -> () | _ -> failwith "Did not visit SynValSig in SynMemberSig.Member" + +// https://github.com/dotnet/fsharp/issues/13114 +[] +let ``Issue 13114 - defaultTraverse walks into SynPat.Record`` () = + let visitor = + { new SyntaxVisitorBase<_>() with + member x.VisitExpr(_, _, defaultTraverse, expr) = defaultTraverse expr + + member x.VisitPat(_, defaultTraverse, pat) = + match pat with + | SynPat.Named _ -> Some pat + | _ -> defaultTraverse pat } + + let source = + """ +type R = { A: int } +let f (r: R) = + match r with + | { A = a } -> a +""" + + let parseTree = parseSourceCode ("C:\\test.fs", source) + + match SyntaxTraversal.Traverse(mkPos 5 13, parseTree, visitor) with + | Some(SynPat.Named(ident = SynIdent(ident = ident))) when ident.idText = "a" -> () + | other -> failwith $"defaultTraverse did not walk into SynPat.Record fields, got: %A{other}" + +// https://github.com/dotnet/fsharp/issues/13114 +[] +let ``Issue 13114 - defaultTraverse walks into SynPat.QuoteExpr`` () = + let visitor = + { new SyntaxVisitorBase<_>() with + member x.VisitExpr(_, _, defaultTraverse, expr) = + match expr with + | SynExpr.Const(SynConst.Int32 42, _) -> Some expr + | _ -> defaultTraverse expr + + member x.VisitPat(_, defaultTraverse, pat) = defaultTraverse pat } + + let source = + """ +let f x = + match x with + | <@ 42 @> -> () + | _ -> () +""" + + let parseTree = parseSourceCode ("C:\\test.fs", source) + + match SyntaxTraversal.Traverse(mkPos 4 8, parseTree, visitor) with + | Some(SynExpr.Const(SynConst.Int32 42, _)) -> () + | other -> failwith $"defaultTraverse did not walk into SynPat.QuoteExpr, got: %A{other}" + +// https://github.com/dotnet/fsharp/issues/13114 +[] +let ``Issue 13114 - defaultTraverse walks into SynPat.FromParseError`` () = + let visitor = + { new SyntaxVisitorBase<_>() with + member x.VisitExpr(_, _, defaultTraverse, expr) = defaultTraverse expr + + member x.VisitPat(_, defaultTraverse, pat) = + match pat with + | SynPat.Named _ -> Some pat + | _ -> defaultTraverse pat } + + // SynPat.FromParseError wraps the inner pat when a parse error occurs; + // defaultTraverse should descend into the wrapped pattern. + let source = + """ +let f x = + match x with + | (a) -> a +""" + + let parseTree = parseSourceCode ("C:\\test.fs", source) + + match SyntaxTraversal.Traverse(mkPos 4 7, parseTree, visitor) with + | Some(SynPat.Named _) -> () + | other -> failwith $"defaultTraverse did not walk into nested SynPat, got: %A{other}" + +// https://github.com/dotnet/fsharp/issues/13114 +[] +let ``Issue 13114 - defaultTraverse walks into SynPat.IsInst`` () = + let visitor = + { new SyntaxVisitorBase<_>() with + member x.VisitExpr(_, _, defaultTraverse, expr) = defaultTraverse expr + + member x.VisitPat(_, defaultTraverse, pat) = defaultTraverse pat + + member x.VisitType(_, _, ty) = + match ty with + | SynType.LongIdent _ -> Some ty + | _ -> None } + + let source = + """ +let f (x: obj) = + match x with + | :? string -> () + | _ -> () +""" + + let parseTree = parseSourceCode ("C:\\test.fs", source) + + match SyntaxTraversal.Traverse(mkPos 4 11, parseTree, visitor) with + | Some(SynType.LongIdent _) -> () + | other -> failwith $"defaultTraverse did not walk into SynPat.IsInst, got: %A{other}" + From 2bd53982e0e5e4535327403185af59d612da67a4 Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Tue, 14 Apr 2026 20:24:40 +0200 Subject: [PATCH 06/11] Fix #12386, SRTP trait call correct overload resolution in multi-submit FSI scenario (#19471) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix SRTP overload resolution across FSI submissions (#12386) Root cause: The TTrait solution ref cell is shared between typar constraints and expression tree nodes (TOp.TraitCall). After typechecking, the codegen/ optimizer path calls SolveMemberConstraint with NoTrace, permanently mutating the shared ref cell. In FSI, this mutation bleeds into the TcEnv carried to the next submission, causing SolveMemberConstraint to return early with a stale solution that doesn't unify the return type — triggering FS0030. In compiled code (cross-DLL), pickling naturally creates fresh ref cells, so the consumer starts with clean constraints. FSI had no equivalent isolation. Fix: At generalization time in GeneralizeVal, when running in interactive mode, decouple the typar constraints' TTrait solution ref cells from the expression tree by cloning with fresh ref cells (TraitConstraintInfo.CloneWithFreshSolution). This way: - Same-submission call sites still see the solution (via the typar's own cell, which has the canonicalization result copied at clone time) - Codegen mutates the expression tree's original cell (unaffected) - Next submission instantiates from the typar's decoupled cell (no codegen contamination) The fix is guarded by isInteractive so it has zero impact on batch compilation. This matches the cross-DLL behavior where pickling creates independent cells. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add regression test for #12386: SRTP trait call correct overload resolution Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix fantomas formatting in TypedTreeOps.Remap.fs Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Checking/Expressions/CheckExpressions.fs | 5 ++ src/Compiler/TypedTree/TypedTree.fs | 4 ++ src/Compiler/TypedTree/TypedTree.fsi | 2 + src/Compiler/TypedTree/TypedTreeOps.Remap.fs | 14 ++++ src/Compiler/TypedTree/TypedTreeOps.Remap.fsi | 3 + .../ConstraintSolver/MemberConstraints.fs | 28 ++++++++ .../Scripting/Interactive.fs | 70 +++++++++++++++++++ 7 files changed, 126 insertions(+) diff --git a/src/Compiler/Checking/Expressions/CheckExpressions.fs b/src/Compiler/Checking/Expressions/CheckExpressions.fs index 5a6bbf619fa..ff9f3c89bf0 100644 --- a/src/Compiler/Checking/Expressions/CheckExpressions.fs +++ b/src/Compiler/Checking/Expressions/CheckExpressions.fs @@ -1690,6 +1690,11 @@ let GeneralizeVal (cenv: cenv) denv enclosingDeclaredTypars generalizedTyparsFor // This is just about the only place we form a GeneralizedType let tyScheme = GeneralizedType(generalizedTypars, ty) + // Decouple SRTP solution ref cells so codegen mutations don't bleed across FSI submissions. + // In compiled code, pickling naturally creates fresh cells. See #12386. + if cenv.g.isInteractive then + decoupleTraitSolutions generalizedTypars + PrelimVal2(id, tyScheme, prelimValReprInfo, memberInfoOpt, isMutable, inlineFlag, baseOrThis, argAttribs, vis, isCompGen, hasDeclaredTypars) let GeneralizeVals (cenv: cenv) denv enclosingDeclaredTypars generalizedTypars types = diff --git a/src/Compiler/TypedTree/TypedTree.fs b/src/Compiler/TypedTree/TypedTree.fs index 995071138b5..733b269b588 100644 --- a/src/Compiler/TypedTree/TypedTree.fs +++ b/src/Compiler/TypedTree/TypedTree.fs @@ -2631,6 +2631,10 @@ type TraitConstraintInfo = with get() = (let (TTrait(solution = sln)) = x in sln.Value) and set v = (let (TTrait(solution = sln)) = x in sln.Value <- v) + member x.CloneWithFreshSolution() = + let (TTrait(a, b, c, d, e, f, sln)) = x + TTrait(a, b, c, d, e, f, ref sln.Value) + member x.WithMemberKind(kind) = (let (TTrait(a, b, c, d, e, f, g)) = x in TTrait(a, b, { c with MemberKind=kind }, d, e, f, g)) member x.WithSupportTypes(tys) = (let (TTrait(_, b, c, d, e, f, g)) = x in TTrait(tys, b, c, d, e, f, g)) diff --git a/src/Compiler/TypedTree/TypedTree.fsi b/src/Compiler/TypedTree/TypedTree.fsi index 0cd4bfd2305..7fbe446641a 100644 --- a/src/Compiler/TypedTree/TypedTree.fsi +++ b/src/Compiler/TypedTree/TypedTree.fsi @@ -1791,6 +1791,8 @@ type TraitConstraintInfo = /// Get or set the solution of the member constraint during inference member Solution: TraitConstraintSln option with get, set + member CloneWithFreshSolution: unit -> TraitConstraintInfo + /// The member kind is irrelevant to the logical properties of a trait. However it adjusts /// the extension property MemberDisplayNameCore member WithMemberKind: SynMemberKind -> TraitConstraintInfo diff --git a/src/Compiler/TypedTree/TypedTreeOps.Remap.fs b/src/Compiler/TypedTree/TypedTreeOps.Remap.fs index b598accceff..605b189ce17 100644 --- a/src/Compiler/TypedTree/TypedTreeOps.Remap.fs +++ b/src/Compiler/TypedTree/TypedTreeOps.Remap.fs @@ -497,6 +497,20 @@ module internal TypeRemapping = let copySlotSig ss = remapSlotSig (fun _ -> []) Remap.Empty ss + /// Decouple SRTP constraint solution ref cells on typars from any shared expression-tree nodes. + /// In FSI, codegen mutates shared TTrait solution cells after typechecking; decoupling at + /// generalization prevents stale solutions from bleeding into subsequent submissions. See #12386. + let decoupleTraitSolutions (typars: Typars) = + for tp in typars do + tp.SetConstraints( + tp.Constraints + |> List.map (fun cx -> + match cx with + | TyparConstraint.MayResolveMember(traitInfo, m) -> + TyparConstraint.MayResolveMember(traitInfo.CloneWithFreshSolution(), m) + | c -> c) + ) + let mkTyparToTyparRenaming tpsorig tps = let tinst = generalizeTypars tps mkTyparInst tpsorig tinst, tinst diff --git a/src/Compiler/TypedTree/TypedTreeOps.Remap.fsi b/src/Compiler/TypedTree/TypedTreeOps.Remap.fsi index 090d07beeb1..04cc615a670 100644 --- a/src/Compiler/TypedTree/TypedTreeOps.Remap.fsi +++ b/src/Compiler/TypedTree/TypedTreeOps.Remap.fsi @@ -193,6 +193,9 @@ module internal TypeRemapping = /// Copy a method slot signature, including new generic type parameters if the slot signature represents a generic method val copySlotSig: SlotSig -> SlotSig + /// Decouple SRTP constraint solution ref cells on typars from shared expression-tree nodes. + val decoupleTraitSolutions: Typars -> unit + val mkTyparToTyparRenaming: Typars -> Typars -> TyparInstantiation * TTypes val mkTyconInst: Tycon -> TypeInst -> TyparInstantiation diff --git a/tests/FSharp.Compiler.ComponentTests/ConstraintSolver/MemberConstraints.fs b/tests/FSharp.Compiler.ComponentTests/ConstraintSolver/MemberConstraints.fs index 9e04bc75dfc..d9b31531d15 100644 --- a/tests/FSharp.Compiler.ComponentTests/ConstraintSolver/MemberConstraints.fs +++ b/tests/FSharp.Compiler.ComponentTests/ConstraintSolver/MemberConstraints.fs @@ -114,6 +114,34 @@ ignore ["1" .. "42"] |> withSingleDiagnostic (Error 1, Line 2, Col 9, Line 2, Col 12, "The type 'string' does not support the operator 'op_Range'") + // https://github.com/dotnet/fsharp/issues/12386 + [] + let ``Issue 12386 - SRTP trait call should resolve correct overload at runtime`` () = + FSharp + """ +type A = + | A + static member ($) (A, _a: float) = 0.0 + static member ($) (A, _a: decimal) = 0M + static member ($) (A, _a: 't) = 0 + +let inline call x = ($) A x + +[] +let main _ = + let resultFloat = call 42.0 + let resultDecimal = call 42M + let resultInt = call 42 + if resultFloat <> 0.0 then failwithf "Expected 0.0 but got %A" resultFloat + if resultDecimal <> 0M then failwithf "Expected 0M but got %A" resultDecimal + if resultInt <> 0 then failwithf "Expected 0 but got %A" resultInt + printfn "All SRTP overload resolutions correct" + 0 + """ + |> asExe + |> compileExeAndRun + |> shouldSucceed + // https://github.com/dotnet/fsharp/issues/6648 [] let ``Issue 6648 - DU of DUs with inline static members should compile`` () = diff --git a/tests/FSharp.Compiler.ComponentTests/Scripting/Interactive.fs b/tests/FSharp.Compiler.ComponentTests/Scripting/Interactive.fs index ea7e3fbe312..0d597f182e2 100644 --- a/tests/FSharp.Compiler.ComponentTests/Scripting/Interactive.fs +++ b/tests/FSharp.Compiler.ComponentTests/Scripting/Interactive.fs @@ -153,6 +153,76 @@ module MultiEmit = """if B.v <> 9.3 then failwith $"9: Failed {A.v} <> 9.3" """ |] |> Seq.iter(fun item -> item |> scriptIt) + // https://github.com/dotnet/fsharp/issues/12386 + // The bug manifests when the SRTP-constrained inline function is defined in one FSI submission + // and called in a separate submission. Single-unit compilation works fine. + // When a type has 2+ specific overloads plus a generic catch-all for operator ($), the SRTP + // constraint stays unresolved across submissions, causing value restriction (FS0030) or wrong + // runtime dispatch (NullReferenceException). Works fine within a single submission and in + // multi-file compiled projects. + [] + [] + [] + let ``Issue 12386 - SRTP trait call resolves correct overload across FSI submissions`` (useMultiEmit) = + let args: string array = [| if useMultiEmit then "--multiemit+" else "--multiemit-" |] + use session = new FSharpScript(additionalArgs = args) + + // Submission 1: Define type with overloaded ($) and an inline function using SRTP + session.Eval( + """ +type A = A with + static member ($) (A, a: float ) = 0.0 + static member ($) (A, a: decimal) = 0M + static member ($) (A, a: 't ) = 0 + +let inline call x = ($) A x +""" + ) + |> ignoreValue + + // Submission 2: Call with float - should resolve to the float overload, not the generic one + let result = session.Eval("call 42.") |> getValue + let fsiVal = result.Value + Assert.Equal(typeof, fsiVal.ReflectionType) + Assert.Equal(0.0, fsiVal.ReflectionValue :?> float) + + // Submission 3: Call with decimal - should resolve to the decimal overload + let result2 = session.Eval("call 42M") |> getValue + let fsiVal2 = result2.Value + Assert.Equal(typeof, fsiVal2.ReflectionType) + Assert.Equal(0M, fsiVal2.ReflectionValue :?> decimal) + + // Same scenario as Issue 12386 but via compiled cross-project reference (not FSI). + // This verifies whether the bug is FSI-specific or also affects project references. + [] + let ``Issue 12386 - SRTP trait call resolves correct overload across project references`` () = + let lib = + FSharp + """ +namespace Lib +type A = A with + static member ($) (A, a: float ) = 0.0 + static member ($) (A, a: decimal) = 0M + static member ($) (A, a: 't ) = 0 + +module Calls = + let inline call x = ($) A x +""" + |> asLibrary + + FSharp + """ +module App +open Lib.Calls + +let result = call 42. +if result <> 0.0 then failwithf "Expected 0.0 but got %A" result +""" + |> withReferences [ lib ] + |> asExe + |> compileExeAndRun + |> shouldSucceed + [] let ``Version directive displays version and environment info``() = Fsx """ From 763cf17b61ef56850c636bb58785dca775d86ce7 Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Tue, 14 Apr 2026 20:39:56 +0200 Subject: [PATCH 07/11] Fix #3939: Hide compiler-generated auto-property symbols from Symbols API (#19498) * Fix #3939: Hide compiler-generated auto-property symbols from Symbols API Fixes https://github.com/dotnet/fsharp/issues/3939 Mark the compiler-generated `v` setter parameter and backing field identifiers in auto-property desugaring with synthetic ranges, so they are excluded from GetAllUsesOfAllSymbolsInFile(). Previously these internal symbols leaked through the API with empty EnclosingEntity and misleading DisplayName/CompiledName values. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix CI: update ProjectAnalysisTests to match synthetic auto-property symbols Remove compiler-generated auto-property backing fields and setter parameter 'v' from expected symbol arrays in 'Test Project24 all symbols' and 'Test symbol uses of properties with both getters and setters'. These symbols are now correctly hidden by MakeSynthetic() ranges. Also merge with latest main. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Bump FSBuildVersion to 101: packages 11.0.100 and 43.12.100 already published Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Compiler/Checking/CheckDeclarations.fs | 6 +++--- .../ProjectAnalysisTests.fs | 12 ----------- .../FSharp.Compiler.Service.Tests/Symbols.fs | 21 ++++++++++++------- 3 files changed, 16 insertions(+), 23 deletions(-) diff --git a/src/Compiler/Checking/CheckDeclarations.fs b/src/Compiler/Checking/CheckDeclarations.fs index f425f49dafc..3d80f056de0 100644 --- a/src/Compiler/Checking/CheckDeclarations.fs +++ b/src/Compiler/Checking/CheckDeclarations.fs @@ -4418,7 +4418,7 @@ module TcDeclarations = // Only the keep the field-targeted attributes let attribs = attribs |> List.filter (fun a -> match a.Target with Some t when t.idText = "field" -> true | _ -> false) let mLetPortion = synExpr.Range - let fldId = ident (CompilerGeneratedName id.idText, mLetPortion) + let fldId = ident (CompilerGeneratedName id.idText, mLetPortion.MakeSynthetic()) let headPat = SynPat.LongIdent (SynLongIdent([fldId], [], [None]), None, Some noInferredTypars, SynArgPats.Pats [], None, mLetPortion) let retInfo = match tyOpt with None -> None | Some ty -> Some (None, SynReturnInfo((ty, SynInfo.unnamedRetVal), ty.Range)) let isMutable = @@ -4446,7 +4446,7 @@ module TcDeclarations = let mMemberPortion = id.idRange // Only the keep the non-field-targeted attributes let attribs = attribs |> List.filter (fun a -> match a.Target with Some t when t.idText = "field" -> false | _ -> true) - let fldId = ident (CompilerGeneratedName id.idText, mMemberPortion) + let fldId = ident (CompilerGeneratedName id.idText, mMemberPortion.MakeSynthetic()) let headPatIds = if isStatic then [id] else [ident ("__", mMemberPortion);id] let headPat = SynPat.LongIdent (SynLongIdent(headPatIds, [], List.replicate headPatIds.Length None), None, Some noInferredTypars, SynArgPats.Pats [], None, mMemberPortion) let memberFlags = { memberFlags with GetterOrSetterIsCompilerGenerated = true } @@ -4475,7 +4475,7 @@ module TcDeclarations = | SynMemberKind.PropertySet | SynMemberKind.PropertyGetSet -> let setter = - let vId = ident("v", mMemberPortion) + let vId = ident("v", mMemberPortion.MakeSynthetic()) let headPat = SynPat.LongIdent (SynLongIdent(headPatIds, [], List.replicate headPatIds.Length None), None, Some noInferredTypars, SynArgPats.Pats [mkSynPatVar None vId], None, mMemberPortion) let rhsExpr = mkSynAssign (SynExpr.Ident fldId) (SynExpr.Ident vId) let binding = mkSynBinding (xmlDoc, headPat) (setterAccess, false, false, mMemberPortion, DebugPointAtBinding.NoneAtInvisible, None, rhsExpr, rhsExpr.Range, [], [], Some memberFlagsForSet, SynBindingTrivia.Zero) diff --git a/tests/FSharp.Compiler.Service.Tests/ProjectAnalysisTests.fs b/tests/FSharp.Compiler.Service.Tests/ProjectAnalysisTests.fs index cd494f7e659..551bdf02ea2 100644 --- a/tests/FSharp.Compiler.Service.Tests/ProjectAnalysisTests.fs +++ b/tests/FSharp.Compiler.Service.Tests/ProjectAnalysisTests.fs @@ -3512,12 +3512,6 @@ let ``Test Project24 all symbols`` () = ("v", "file1", ((22, 17), (22, 18)), ["defn"], []); ("int", "file1", ((25, 21), (25, 24)), ["type"], ["abbrev"]); ("v", "file1", ((25, 18), (25, 19)), ["defn"], []); - ("``AutoPropGet@``", "file1", ((27, 15), (27, 26)), [], ["compgen"]); - ("``AutoPropGetSet@``", "file1", ((28, 15), (28, 29)), [], ["compgen"; "mutable"]) - ("v", "file1", ((28, 15), (28, 29)), ["defn"], []); - ("``StaticAutoPropGet@``", "file1", ((30, 22), (30, 39)), [], ["compgen"]); - ("``StaticAutoPropGetSet@``", "file1", ((31, 22), (31, 42)), [], - ["compgen"; "mutable"]); ("v", "file1", ((31, 22), (31, 42)), ["defn"], []); ("``.cctor``", "file1", ((4, 5), (4, 23)), ["defn"], ["member"]); ("TypeWithProperties", "file1", ((33, 9), (33, 27)), [], ["member"; "ctor"]); ("NameGetSet", "file1", ((33, 9), (33, 40)), [], ["member"; "prop"]); @@ -3614,12 +3608,6 @@ let ``Test symbol uses of properties with both getters and setters`` () = ("v", "file1", ((22, 17), (22, 18)), []); ("int", "file1", ((25, 21), (25, 24)), ["abbrev"]); ("v", "file1", ((25, 18), (25, 19)), []); - ("``AutoPropGet@``", "file1", ((27, 15), (27, 26)), ["compgen"]); - ("``AutoPropGetSet@``", "file1", ((28, 15), (28, 29)), ["compgen"; "mutable"]); - ("v", "file1", ((28, 15), (28, 29)), []); - ("``StaticAutoPropGet@``", "file1", ((30, 22), (30, 39)), ["compgen"]); - ("``StaticAutoPropGetSet@``", "file1", ((31, 22), (31, 42)), - ["compgen"; "mutable"]); ("v", "file1", ((31, 22), (31, 42)), []); ("``.cctor``", "file1", ((4, 5), (4, 23)), ["member"]); ("TypeWithProperties", "file1", ((33, 9), (33, 27)), ["member"; "ctor"]); ("NameGetSet", "file1", ((33, 9), (33, 40)), ["member"; "prop"]); diff --git a/tests/FSharp.Compiler.Service.Tests/Symbols.fs b/tests/FSharp.Compiler.Service.Tests/Symbols.fs index 3f4d4fcdbd9..69bfb70519a 100644 --- a/tests/FSharp.Compiler.Service.Tests/Symbols.fs +++ b/tests/FSharp.Compiler.Service.Tests/Symbols.fs @@ -634,18 +634,23 @@ type Foo = Assert.True(setMfv.CompiledName.StartsWith("set_")) | _ -> failwith $"Expected three symbols, got %A{symbols}" - [] - let ``AutoProperty with get, set has property symbol 02`` () = - let symbol = Checker.getSymbolUse """ + // https://github.com/dotnet/fsharp/issues/3939 + [] + let ``AutoProperty with get, set does not expose compiler-generated v symbol`` () = + let _, checkResults = getParseAndCheckResults """ namespace Foo type Foo = - member val AutoPropGetSet{caret} = 0 with get, set + member val AutoPropGetSet = 0 with get, set """ - // The setter should have a symbol for the generated parameter `v`. - let setVMfv = symbol |> chooseMemberOrFunctionOrValue - if Option.isNone setVMfv then - failwith "No generated v symbol for the setter was found" + let allSymbols = checkResults.GetAllUsesOfAllSymbolsInFile() + let allMfvs = allSymbols |> Seq.choose (fun su -> match su.Symbol with :? FSharpMemberOrFunctionOrValue as mfv -> Some mfv | _ -> None) |> Seq.toList + // The compiler-generated `v` setter parameter should NOT appear in symbol uses + let vSymbols = allMfvs |> List.filter (fun mfv -> mfv.DisplayName = "v") + Assert.True(vSymbols.IsEmpty, $"Compiler-generated 'v' symbol should not be exposed via GetAllUsesOfAllSymbolsInFile, but found {vSymbols.Length} occurrences") + // The compiler-generated backing field should also not appear + let backingFieldSymbols = allMfvs |> List.filter (fun mfv -> mfv.DisplayName.Contains("@")) + Assert.True(backingFieldSymbols.IsEmpty, $"Compiler-generated backing field should not appear, but found: {backingFieldSymbols |> List.map (fun m -> m.DisplayName)}") [] let ``Property symbol is resolved for property`` () = From 872352f261b22e130d03c5506cd06fbaa71a8234 Mon Sep 17 00:00:00 2001 From: Edgar Gonzalez Date: Tue, 14 Apr 2026 14:34:17 -0500 Subject: [PATCH 08/11] Fix AttributeUsage.AllowMultiple not inherited for C#-defined attributes (#19315) * Fix AttributeUsage.AllowMultiple not inherited for C#-defined attributes (#17107) The F# compiler was not walking the inheritance chain for IL-imported (C#) attribute types when checking AllowMultiple. The supersOfTyconRef function only used tcaug_super, which is not populated for IL types. This fix simplifies TryFindAttributeUsageAttribute to only check the current type, and adds a recursive allowsMultiple function in PostInferenceChecks that walks the inheritance chain using GetSuperTypeOfType, which correctly handles both F# and IL-imported types. Based on work by edgarfgp in PR #19315. Fixes #17107 Co-authored-by: edgarfgp <31915729+edgarfgp@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Use O(1) WellKnownILAttributes fast-path for AttributeUsage lookup Switch TryFindAttributeUsageAttribute to use tryBindTyconRefAttributeCore with ValueSome WellKnownILAttributes.AttributeUsageAttribute, enabling O(1) early exit on IL types that lack [AttributeUsage]. This avoids a full linear attribute scan at each level of the inheritance chain walk. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Tomas Grosup Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../.FSharp.Compiler.Service/11.0.100.md | 1 + src/Compiler/Checking/PostInferenceChecks.fs | 15 ++- .../TypedTree/TypedTreeOps.Attributes.fs | 45 ++++--- .../TypedTree/TypedTreeOps.Attributes.fsi | 2 +- .../TypedTree/TypedTreeOps.FreeVars.fs | 8 -- .../TypedTree/TypedTreeOps.FreeVars.fsi | 3 - .../Language/AttributeCheckingTests.fs | 111 ++++++++++++++++++ 7 files changed, 148 insertions(+), 37 deletions(-) diff --git a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md index 7d2823606ed..7751906ad10 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md +++ b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md @@ -25,6 +25,7 @@ * Fix nativeptr in interfaces leads to TypeLoadException. (Issue [#14508](https://github.com/dotnet/fsharp/issues/14508), [PR #19338](https://github.com/dotnet/fsharp/pull/19338)) * Fix box instruction for literal upcasts. (Issue [#18319](https://github.com/dotnet/fsharp/issues/18319), [PR #19338](https://github.com/dotnet/fsharp/pull/19338)) * Fix Decimal Literal causes InvalidProgramException in Debug builds. (Issue [#18956](https://github.com/dotnet/fsharp/issues/18956), [PR #19338](https://github.com/dotnet/fsharp/pull/19338)) +* Fix `AttributeUsage.AllowMultiple` not being inherited for attributes subclassed in C#. ([Issue #17107](https://github.com/dotnet/fsharp/issues/17107), [PR #19315](https://github.com/dotnet/fsharp/pull/19315)) ### Added diff --git a/src/Compiler/Checking/PostInferenceChecks.fs b/src/Compiler/Checking/PostInferenceChecks.fs index 09e95d5bbf1..7924394c743 100644 --- a/src/Compiler/Checking/PostInferenceChecks.fs +++ b/src/Compiler/Checking/PostInferenceChecks.fs @@ -2036,8 +2036,19 @@ and CheckAttribs cenv env (attribs: Attribs) = |> Seq.filter (fun (_, count) -> count > 1) |> Seq.map fst |> Seq.toList - // Filter for allowMultiple = false - |> List.filter (fun (tcref, _, m) -> TryFindAttributeUsageAttribute cenv.g m tcref <> Some true) + // Filter for allowMultiple = false, walking the inheritance chain to find AttributeUsage + |> List.filter (fun (tcref, _, m) -> + let rec allowsMultiple (tcref: TyconRef) = + match TryFindAttributeUsageAttribute cenv.g m tcref with + | Some res -> res + | None -> + generalizedTyconRef cenv.g tcref + |> GetSuperTypeOfType cenv.g cenv.amap m + |> Option.bind (tryTcrefOfAppTy cenv.g >> ValueOption.toOption) + |> Option.map allowsMultiple + |> Option.defaultValue false + + not (allowsMultiple tcref)) if cenv.reportErrors then for tcref, _, m in duplicates do diff --git a/src/Compiler/TypedTree/TypedTreeOps.Attributes.fs b/src/Compiler/TypedTree/TypedTreeOps.Attributes.fs index 488cc8e2521..a57c6e23804 100644 --- a/src/Compiler/TypedTree/TypedTreeOps.Attributes.fs +++ b/src/Compiler/TypedTree/TypedTreeOps.Attributes.fs @@ -812,30 +812,29 @@ module internal AttributeHelpers = | [ Some(:? bool as v: obj) ], _ -> Some v | _ -> None) - /// Try to find the resolved attributeusage for an type by walking its inheritance tree and picking the correct attribute usage value + /// Try to find the AllowMultiple value of the AttributeUsage attribute on a type definition. let TryFindAttributeUsageAttribute g m tcref = - [| yield tcref; yield! supersOfTyconRef tcref |] - |> Array.tryPick (fun tcref -> - TryBindTyconRefAttribute - g - m - g.attrib_AttributeUsageAttribute - tcref - (fun (_, named) -> - named - |> List.tryPick (function - | "AllowMultiple", _, _, ILAttribElem.Bool res -> Some res - | _ -> None)) - (fun (Attrib(_, _, _, named, _, _, _)) -> - named - |> List.tryPick (function - | AttribNamedArg("AllowMultiple", _, _, AttribBoolArg res) -> Some res - | _ -> None)) - (fun (_, named) -> - named - |> List.tryPick (function - | "AllowMultiple", Some(:? bool as res: obj) -> Some res - | _ -> None))) + tryBindTyconRefAttributeCore + g + m + (ValueSome WellKnownILAttributes.AttributeUsageAttribute) + g.attrib_AttributeUsageAttribute + tcref + (fun (_, named) -> + named + |> List.tryPick (function + | "AllowMultiple", _, _, ILAttribElem.Bool res -> Some res + | _ -> None)) + (fun (Attrib(_, _, _, named, _, _, _)) -> + named + |> List.tryPick (function + | AttribNamedArg("AllowMultiple", _, _, AttribBoolArg res) -> Some res + | _ -> None)) + (fun (_, named) -> + named + |> List.tryPick (function + | "AllowMultiple", Some(:? bool as res: obj) -> Some res + | _ -> None)) /// Try to find a specific attribute on a type definition, where the attribute accepts a string argument. /// diff --git a/src/Compiler/TypedTree/TypedTreeOps.Attributes.fsi b/src/Compiler/TypedTree/TypedTreeOps.Attributes.fsi index 76096c3d65e..8fc3910354a 100644 --- a/src/Compiler/TypedTree/TypedTreeOps.Attributes.fsi +++ b/src/Compiler/TypedTree/TypedTreeOps.Attributes.fsi @@ -194,7 +194,7 @@ module internal AttributeHelpers = /// Check if a TyconRef has AllowNullLiteralAttribute, returning Some true/Some false/None. val TyconRefAllowsNull: g: TcGlobals -> tcref: TyconRef -> bool option - /// Try to find the AttributeUsage attribute, looking for the value of the AllowMultiple named parameter + /// Try to find the AllowMultiple value of the AttributeUsage attribute on a type definition. val TryFindAttributeUsageAttribute: TcGlobals -> range -> TyconRef -> bool option val (|AttribBitwiseOrExpr|_|): TcGlobals -> Expr -> (Expr * Expr) voption diff --git a/src/Compiler/TypedTree/TypedTreeOps.FreeVars.fs b/src/Compiler/TypedTree/TypedTreeOps.FreeVars.fs index 2c84aeb7b76..92649150c9d 100644 --- a/src/Compiler/TypedTree/TypedTreeOps.FreeVars.fs +++ b/src/Compiler/TypedTree/TypedTreeOps.FreeVars.fs @@ -1542,11 +1542,3 @@ module internal MemberRepresentation = match tycon.TypeContents.tcaug_super with | None -> g.obj_ty_noNulls | Some ty -> ty - - /// walk a TyconRef's inheritance tree, yielding any parent types as an array - let supersOfTyconRef (tcref: TyconRef) = - tcref - |> Array.unfold (fun tcref -> - match tcref.TypeContents.tcaug_super with - | Some(TType_app(sup, _, _)) -> Some(sup, sup) - | _ -> None) diff --git a/src/Compiler/TypedTree/TypedTreeOps.FreeVars.fsi b/src/Compiler/TypedTree/TypedTreeOps.FreeVars.fsi index e7e37aef6e3..1787715a6e3 100644 --- a/src/Compiler/TypedTree/TypedTreeOps.FreeVars.fsi +++ b/src/Compiler/TypedTree/TypedTreeOps.FreeVars.fsi @@ -357,9 +357,6 @@ module internal MemberRepresentation = val superOfTycon: TcGlobals -> Tycon -> TType - /// walk a TyconRef's inheritance tree, yielding any parent types as an array - val supersOfTyconRef: TyconRef -> TyconRef array - val GetTraitConstraintInfosOfTypars: TcGlobals -> Typars -> TraitConstraintInfo list val GetTraitWitnessInfosOfTypars: TcGlobals -> numParentTypars: int -> typars: Typars -> TraitWitnessInfos diff --git a/tests/FSharp.Compiler.ComponentTests/Language/AttributeCheckingTests.fs b/tests/FSharp.Compiler.ComponentTests/Language/AttributeCheckingTests.fs index d91997b68f0..898706bca5c 100644 --- a/tests/FSharp.Compiler.ComponentTests/Language/AttributeCheckingTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/Language/AttributeCheckingTests.fs @@ -77,3 +77,114 @@ type C() = |> withReferences [csharpBaseClass] |> compile |> shouldSucceed + + [] + let ``C# attribute subclass inherits AllowMultiple true from base`` () = + let csharpLib = + CSharp """ + using System; + + [AttributeUsage(AttributeTargets.All, AllowMultiple = true)] + public class BaseAttribute : Attribute { } + public class ChildAttribute : BaseAttribute { } + """ |> withName "csAttrLib" + + FSharp """ +module Test + +[] +type C() = class end + """ + |> withReferences [csharpLib] + |> compile + |> shouldSucceed + + [] + let ``C# attribute subclass inherits AllowMultiple false from base`` () = + let csharpLib = + CSharp """ + using System; + + [AttributeUsage(AttributeTargets.All, AllowMultiple = false)] + public class BaseAttribute : Attribute { } + public class ChildAttribute : BaseAttribute { } + """ |> withName "csAttrLib" + + FSharp """ +module Test + +[] +type C() = class end + """ + |> withReferences [csharpLib] + |> compile + |> shouldFail + |> withSingleDiagnostic (Error 429, Line 4, Col 10, Line 4, Col 15, "The attribute type 'ChildAttribute' has 'AllowMultiple=false'. Multiple instances of this attribute cannot be attached to a single language element.") + + [] + let ``C# attribute multi-level inheritance inherits AllowMultiple true`` () = + let csharpLib = + CSharp """ + using System; + + [AttributeUsage(AttributeTargets.All, AllowMultiple = true)] + public class BaseAttribute : Attribute { } + public class MiddleAttribute : BaseAttribute { } + public class LeafAttribute : MiddleAttribute { } + """ |> withName "csAttrLib" + + FSharp """ +module Test + +[] +type C() = class end + """ + |> withReferences [csharpLib] + |> compile + |> shouldSucceed + + [] + let ``C# attribute subclass with own AttributeUsage overrides base AllowMultiple`` () = + let csharpLib = + CSharp """ + using System; + + [AttributeUsage(AttributeTargets.All, AllowMultiple = true)] + public class BaseAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.All, AllowMultiple = false)] + public class ChildAttribute : BaseAttribute { } + """ |> withName "csAttrLib" + + FSharp """ +module Test + +[] +type C() = class end + """ + |> withReferences [csharpLib] + |> compile + |> shouldFail + |> withSingleDiagnostic (Error 429, Line 4, Col 10, Line 4, Col 15, "The attribute type 'ChildAttribute' has 'AllowMultiple=false'. Multiple instances of this attribute cannot be attached to a single language element.") + + [] + let ``F# attribute subclass of C# base inherits AllowMultiple true`` () = + let csharpLib = + CSharp """ + using System; + + [AttributeUsage(AttributeTargets.All, AllowMultiple = true)] + public class BaseAttribute : Attribute { } + """ |> withName "csAttrLib" + + FSharp """ +module Test + +type ChildAttribute() = inherit BaseAttribute() + +[] +type C() = class end + """ + |> withReferences [csharpLib] + |> compile + |> shouldSucceed From ce2a17ae249162a3436f4a298535b3f264807ee6 Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Wed, 15 Apr 2026 08:53:53 +0200 Subject: [PATCH 09/11] Fix flaky CancellationPropagatesToTask test (#19569) Add synchronization to ensure the async computation has actually started running on the thread pool before cancelling the default token. Without this, under heavy CI load the thread pool may not schedule the async within the 1-second wait window, causing the task to never transition to Canceled state before the timeout. This mirrors the pattern already used by the CancellationPropagatesToGroup test which uses a ManualResetEvent for the same purpose. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../FSharp.Core/Microsoft.FSharp.Control/AsyncType.fs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Control/AsyncType.fs b/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Control/AsyncType.fs index 3bf50a19fb3..d1d09dcd8a1 100644 --- a/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Control/AsyncType.fs +++ b/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Control/AsyncType.fs @@ -255,10 +255,13 @@ type AsyncType() = [] member _.CancellationPropagatesToTask () = + let ewh = new ManualResetEvent(false) let a = async { + ewh.Set() |> Assert.True while true do () } let t = Async.StartAsTask a + ewh.WaitOne() |> Assert.True Async.CancelDefaultToken () let mutable exceptionThrown = false try From ad11468b89e3ba27e8f6c3b95038fc3c049b8dfc Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Wed, 15 Apr 2026 09:24:07 +0200 Subject: [PATCH 10/11] Update dependencies from https://github.com/dotnet/roslyn build 20260414.1 (#19587) On relative base path root Microsoft.CodeAnalysis , Microsoft.CodeAnalysis.Compilers , Microsoft.CodeAnalysis.CSharp , Microsoft.CodeAnalysis.EditorFeatures , Microsoft.CodeAnalysis.EditorFeatures.Text , Microsoft.CodeAnalysis.ExternalAccess.FSharp , Microsoft.CodeAnalysis.Features , Microsoft.VisualStudio.LanguageServices From Version 5.7.0-1.26210.5 -> To Version 5.7.0-1.26214.1 Co-authored-by: dotnet-maestro[bot] --- eng/Version.Details.props | 16 ++++++++-------- eng/Version.Details.xml | 32 ++++++++++++++++---------------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/eng/Version.Details.props b/eng/Version.Details.props index 4290ffadabe..61c3fa5502f 100644 --- a/eng/Version.Details.props +++ b/eng/Version.Details.props @@ -19,14 +19,14 @@ This file should be imported by eng/Versions.props 1.0.0-prerelease.26180.1 1.0.0-prerelease.26180.1 - 5.7.0-1.26210.5 - 5.7.0-1.26210.5 - 5.7.0-1.26210.5 - 5.7.0-1.26210.5 - 5.7.0-1.26210.5 - 5.7.0-1.26210.5 - 5.7.0-1.26210.5 - 5.7.0-1.26210.5 + 5.7.0-1.26214.1 + 5.7.0-1.26214.1 + 5.7.0-1.26214.1 + 5.7.0-1.26214.1 + 5.7.0-1.26214.1 + 5.7.0-1.26214.1 + 5.7.0-1.26214.1 + 5.7.0-1.26214.1 10.0.2 10.0.2 diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 286094f1cec..76ebd34ba05 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -18,37 +18,37 @@ https://github.com/dotnet/msbuild e5ebe15655a6be2b2e3209464d0cde1b8825ab57 - + https://github.com/dotnet/roslyn - 0eca297f565449839436b91fe4aa180f9bcdedd2 + 2396c4d269fa47c6a063885453d8180687d3d9a4 - + https://github.com/dotnet/roslyn - 0eca297f565449839436b91fe4aa180f9bcdedd2 + 2396c4d269fa47c6a063885453d8180687d3d9a4 - + https://github.com/dotnet/roslyn - 0eca297f565449839436b91fe4aa180f9bcdedd2 + 2396c4d269fa47c6a063885453d8180687d3d9a4 - + https://github.com/dotnet/roslyn - 0eca297f565449839436b91fe4aa180f9bcdedd2 + 2396c4d269fa47c6a063885453d8180687d3d9a4 - + https://github.com/dotnet/roslyn - 0eca297f565449839436b91fe4aa180f9bcdedd2 + 2396c4d269fa47c6a063885453d8180687d3d9a4 - + https://github.com/dotnet/roslyn - 0eca297f565449839436b91fe4aa180f9bcdedd2 + 2396c4d269fa47c6a063885453d8180687d3d9a4 - + https://github.com/dotnet/roslyn - 0eca297f565449839436b91fe4aa180f9bcdedd2 + 2396c4d269fa47c6a063885453d8180687d3d9a4 - + https://github.com/dotnet/roslyn - 0eca297f565449839436b91fe4aa180f9bcdedd2 + 2396c4d269fa47c6a063885453d8180687d3d9a4 From aa96b15ade34daebbb2028e94e2c6a6505250f58 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 15 Apr 2026 07:25:14 +0000 Subject: [PATCH 11/11] Reset files to feature/net11-scouting Reset patterns: - global.json - eng/Version.Details.xml - eng/Version.Details.props - eng/Versions.props - eng/common/** - eng/TargetFrameworks.props --- eng/TargetFrameworks.props | 2 +- eng/Version.Details.props | 18 +- eng/Version.Details.xml | 36 +- eng/Versions.props | 9 +- eng/common/SetupNugetSources.ps1 | 17 +- eng/common/SetupNugetSources.sh | 17 +- eng/common/build.ps1 | 2 + eng/common/build.sh | 8 +- eng/common/core-templates/job/job.yml | 8 + .../job/publish-build-assets.yml | 12 +- eng/common/core-templates/job/renovate.yml | 196 +++++++ .../job/source-index-stage1.yml | 6 +- .../post-build/common-variables.yml | 2 - .../core-templates/post-build/post-build.yml | 528 ++++++++---------- eng/common/core-templates/stages/renovate.yml | 111 ++++ .../steps/install-microbuild-impl.yml | 34 ++ .../steps/install-microbuild.yml | 64 ++- .../core-templates/steps/publish-logs.yml | 1 + .../core-templates/steps/source-build.yml | 2 +- .../steps/source-index-stage1-publish.yml | 8 +- eng/common/cross/build-rootfs.sh | 81 ++- eng/common/cross/toolchain.cmake | 15 +- eng/common/darc-init.ps1 | 8 +- eng/common/darc-init.sh | 6 +- eng/common/dotnet-install.sh | 2 +- eng/common/dotnet.sh | 2 +- eng/common/internal-feed-operations.sh | 2 +- eng/common/native/init-distro-rid.sh | 2 + eng/common/native/install-dependencies.sh | 11 +- eng/common/post-build/redact-logs.ps1 | 7 +- eng/common/renovate.env | 42 ++ eng/common/sdk-task.ps1 | 15 +- eng/common/template-guidance.md | 3 - eng/common/tools.ps1 | 105 +--- eng/common/tools.sh | 22 +- global.json | 6 +- 36 files changed, 883 insertions(+), 527 deletions(-) create mode 100644 eng/common/core-templates/job/renovate.yml create mode 100644 eng/common/core-templates/stages/renovate.yml create mode 100644 eng/common/core-templates/steps/install-microbuild-impl.yml create mode 100644 eng/common/renovate.env diff --git a/eng/TargetFrameworks.props b/eng/TargetFrameworks.props index d384e5fbcaa..e3938d0f73a 100644 --- a/eng/TargetFrameworks.props +++ b/eng/TargetFrameworks.props @@ -11,7 +11,7 @@ - net10.0 + net11.0 $([System.Text.RegularExpressions.Regex]::Replace('$(FSharpNetCoreProductTargetFramework)', '^net(\d+)\.0$', '$1')) diff --git a/eng/Version.Details.props b/eng/Version.Details.props index 61c3fa5502f..5933d2dfe98 100644 --- a/eng/Version.Details.props +++ b/eng/Version.Details.props @@ -6,7 +6,7 @@ This file should be imported by eng/Versions.props - 10.0.0-beta.26208.4 + 11.0.0-beta.26211.1 18.6.1 18.6.1 @@ -19,14 +19,14 @@ This file should be imported by eng/Versions.props 1.0.0-prerelease.26180.1 1.0.0-prerelease.26180.1 - 5.7.0-1.26214.1 - 5.7.0-1.26214.1 - 5.7.0-1.26214.1 - 5.7.0-1.26214.1 - 5.7.0-1.26214.1 - 5.7.0-1.26214.1 - 5.7.0-1.26214.1 - 5.7.0-1.26214.1 + 5.7.0-1.26210.5 + 5.7.0-1.26210.5 + 5.7.0-1.26210.5 + 5.7.0-1.26210.5 + 5.7.0-1.26210.5 + 5.7.0-1.26210.5 + 5.7.0-1.26210.5 + 5.7.0-1.26210.5 10.0.2 10.0.2 diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 76ebd34ba05..ed369c7e7ca 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -18,37 +18,37 @@ https://github.com/dotnet/msbuild e5ebe15655a6be2b2e3209464d0cde1b8825ab57 - + https://github.com/dotnet/roslyn - 2396c4d269fa47c6a063885453d8180687d3d9a4 + 0eca297f565449839436b91fe4aa180f9bcdedd2 - + https://github.com/dotnet/roslyn - 2396c4d269fa47c6a063885453d8180687d3d9a4 + 0eca297f565449839436b91fe4aa180f9bcdedd2 - + https://github.com/dotnet/roslyn - 2396c4d269fa47c6a063885453d8180687d3d9a4 + 0eca297f565449839436b91fe4aa180f9bcdedd2 - + https://github.com/dotnet/roslyn - 2396c4d269fa47c6a063885453d8180687d3d9a4 + 0eca297f565449839436b91fe4aa180f9bcdedd2 - + https://github.com/dotnet/roslyn - 2396c4d269fa47c6a063885453d8180687d3d9a4 + 0eca297f565449839436b91fe4aa180f9bcdedd2 - + https://github.com/dotnet/roslyn - 2396c4d269fa47c6a063885453d8180687d3d9a4 + 0eca297f565449839436b91fe4aa180f9bcdedd2 - + https://github.com/dotnet/roslyn - 2396c4d269fa47c6a063885453d8180687d3d9a4 + 0eca297f565449839436b91fe4aa180f9bcdedd2 - + https://github.com/dotnet/roslyn - 2396c4d269fa47c6a063885453d8180687d3d9a4 + 0eca297f565449839436b91fe4aa180f9bcdedd2 @@ -76,9 +76,9 @@ - + https://github.com/dotnet/arcade - ecdd5c6a7986cafabbf6a322ea09a07736a01a0d + a08169b890573cfd7f949ea9062c86a4db1aab1b https://dev.azure.com/dnceng/internal/_git/dotnet-optimization diff --git a/eng/Versions.props b/eng/Versions.props index 8248454c00f..a6a34de7ef5 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -89,7 +89,12 @@ 4.6.1 4.6.3 6.1.2 - + + 10.0.2 + $(SystemPackagesVersion) + $(SystemPackagesVersion) + $(SystemPackagesVersion) + $(SystemPackagesVersion) @@ -156,7 +161,7 @@ 5.0.0-preview.7.20364.11 5.0.0-preview.7.20364.11 - 17.14.1 + 18.0.1 2.0.2 13.0.3 3.2.2 diff --git a/eng/common/SetupNugetSources.ps1 b/eng/common/SetupNugetSources.ps1 index 65ed3a8adef..fc8d618014e 100644 --- a/eng/common/SetupNugetSources.ps1 +++ b/eng/common/SetupNugetSources.ps1 @@ -1,7 +1,6 @@ # This script adds internal feeds required to build commits that depend on internal package sources. For instance, -# dotnet6-internal would be added automatically if dotnet6 was found in the nuget.config file. Similarly, -# dotnet-eng-internal and dotnet-tools-internal are added if dotnet-eng and dotnet-tools are present. -# In addition, this script also enables disabled internal Maestro (darc-int*) feeds. +# dotnet6-internal would be added automatically if dotnet6 was found in the nuget.config file. In addition also enables +# disabled internal Maestro (darc-int*) feeds. # # Optionally, this script also adds a credential entry for each of the internal feeds if supplied. # @@ -174,16 +173,4 @@ foreach ($dotnetVersion in $dotnetVersions) { } } -# Check for dotnet-eng and add dotnet-eng-internal if present -$dotnetEngSource = $sources.SelectSingleNode("add[@key='dotnet-eng']") -if ($dotnetEngSource -ne $null) { - AddOrEnablePackageSource -Sources $sources -DisabledPackageSources $disabledSources -SourceName "dotnet-eng-internal" -SourceEndPoint "https://pkgs.dev.azure.com/dnceng/internal/_packaging/dotnet-eng-internal/nuget/$feedSuffix" -Creds $creds -Username $userName -pwd $Password -} - -# Check for dotnet-tools and add dotnet-tools-internal if present -$dotnetToolsSource = $sources.SelectSingleNode("add[@key='dotnet-tools']") -if ($dotnetToolsSource -ne $null) { - AddOrEnablePackageSource -Sources $sources -DisabledPackageSources $disabledSources -SourceName "dotnet-tools-internal" -SourceEndPoint "https://pkgs.dev.azure.com/dnceng/internal/_packaging/dotnet-tools-internal/nuget/$feedSuffix" -Creds $creds -Username $userName -pwd $Password -} - $doc.Save($filename) diff --git a/eng/common/SetupNugetSources.sh b/eng/common/SetupNugetSources.sh index b2163abbe71..b97cc536379 100755 --- a/eng/common/SetupNugetSources.sh +++ b/eng/common/SetupNugetSources.sh @@ -1,9 +1,8 @@ #!/usr/bin/env bash # This script adds internal feeds required to build commits that depend on internal package sources. For instance, -# dotnet6-internal would be added automatically if dotnet6 was found in the nuget.config file. Similarly, -# dotnet-eng-internal and dotnet-tools-internal are added if dotnet-eng and dotnet-tools are present. -# In addition, this script also enables disabled internal Maestro (darc-int*) feeds. +# dotnet6-internal would be added automatically if dotnet6 was found in the nuget.config file. In addition also enables +# disabled internal Maestro (darc-int*) feeds. # # Optionally, this script also adds a credential entry for each of the internal feeds if supplied. # @@ -174,18 +173,6 @@ for DotNetVersion in ${DotNetVersions[@]} ; do fi done -# Check for dotnet-eng and add dotnet-eng-internal if present -grep -i " /dev/null -if [ "$?" == "0" ]; then - AddOrEnablePackageSource "dotnet-eng-internal" "https://pkgs.dev.azure.com/dnceng/internal/_packaging/dotnet-eng-internal/nuget/$FeedSuffix" -fi - -# Check for dotnet-tools and add dotnet-tools-internal if present -grep -i " /dev/null -if [ "$?" == "0" ]; then - AddOrEnablePackageSource "dotnet-tools-internal" "https://pkgs.dev.azure.com/dnceng/internal/_packaging/dotnet-tools-internal/nuget/$FeedSuffix" -fi - # I want things split line by line PrevIFS=$IFS IFS=$'\n' diff --git a/eng/common/build.ps1 b/eng/common/build.ps1 index 8cfee107e7a..18397a60eb8 100644 --- a/eng/common/build.ps1 +++ b/eng/common/build.ps1 @@ -6,6 +6,7 @@ Param( [string][Alias('v')]$verbosity = "minimal", [string] $msbuildEngine = $null, [bool] $warnAsError = $true, + [string] $warnNotAsError = '', [bool] $nodeReuse = $true, [switch] $buildCheck = $false, [switch][Alias('r')]$restore, @@ -70,6 +71,7 @@ function Print-Usage() { Write-Host " -excludeCIBinarylog Don't output binary log (short: -nobl)" Write-Host " -prepareMachine Prepare machine for CI run, clean up processes after build" Write-Host " -warnAsError Sets warnaserror msbuild parameter ('true' or 'false')" + Write-Host " -warnNotAsError Sets a semi-colon delimited list of warning codes that should not be treated as errors" Write-Host " -msbuildEngine Msbuild engine to use to run build ('dotnet', 'vs', or unspecified)." Write-Host " -excludePrereleaseVS Set to exclude build engines in prerelease versions of Visual Studio" Write-Host " -nativeToolsOnMachine Sets the native tools on machine environment variable (indicating that the script should use native tools on machine)" diff --git a/eng/common/build.sh b/eng/common/build.sh index 9767bb411a4..5883e53bcfb 100755 --- a/eng/common/build.sh +++ b/eng/common/build.sh @@ -42,6 +42,7 @@ usage() echo " --prepareMachine Prepare machine for CI run, clean up processes after build" echo " --nodeReuse Sets nodereuse msbuild parameter ('true' or 'false')" echo " --warnAsError Sets warnaserror msbuild parameter ('true' or 'false')" + echo " --warnNotAsError Sets a semi-colon delimited list of warning codes that should not be treated as errors" echo " --buildCheck Sets /check msbuild parameter" echo " --fromVMR Set when building from within the VMR" echo "" @@ -78,6 +79,7 @@ ci=false clean=false warn_as_error=true +warn_not_as_error='' node_reuse=true build_check=false binary_log=false @@ -92,7 +94,7 @@ runtime_source_feed='' runtime_source_feed_key='' properties=() -while [[ $# > 0 ]]; do +while [[ $# -gt 0 ]]; do opt="$(echo "${1/#--/-}" | tr "[:upper:]" "[:lower:]")" case "$opt" in -help|-h) @@ -176,6 +178,10 @@ while [[ $# > 0 ]]; do warn_as_error=$2 shift ;; + -warnnotaserror) + warn_not_as_error=$2 + shift + ;; -nodereuse) node_reuse=$2 shift diff --git a/eng/common/core-templates/job/job.yml b/eng/common/core-templates/job/job.yml index eaed6d87e65..66c7988f222 100644 --- a/eng/common/core-templates/job/job.yml +++ b/eng/common/core-templates/job/job.yml @@ -19,6 +19,8 @@ parameters: # publishing defaults artifacts: '' enableMicrobuild: false + enablePreviewMicrobuild: false + microbuildPluginVersion: 'latest' enableMicrobuildForMacAndLinux: false microbuildUseESRP: true enablePublishBuildArtifacts: false @@ -71,6 +73,8 @@ jobs: templateContext: ${{ parameters.templateContext }} variables: + - name: AllowPtrToDetectTestRunRetryFiles + value: true - ${{ if ne(parameters.enableTelemetry, 'false') }}: - name: DOTNET_CLI_TELEMETRY_PROFILE value: '$(Build.Repository.Uri)' @@ -128,6 +132,8 @@ jobs: - template: /eng/common/core-templates/steps/install-microbuild.yml parameters: enableMicrobuild: ${{ parameters.enableMicrobuild }} + enablePreviewMicrobuild: ${{ parameters.enablePreviewMicrobuild }} + microbuildPluginVersion: ${{ parameters.microbuildPluginVersion }} enableMicrobuildForMacAndLinux: ${{ parameters.enableMicrobuildForMacAndLinux }} microbuildUseESRP: ${{ parameters.microbuildUseESRP }} continueOnError: ${{ parameters.continueOnError }} @@ -150,6 +156,8 @@ jobs: - template: /eng/common/core-templates/steps/cleanup-microbuild.yml parameters: enableMicrobuild: ${{ parameters.enableMicrobuild }} + enablePreviewMicrobuild: ${{ parameters.enablePreviewMicrobuild }} + microbuildPluginVersion: ${{ parameters.microbuildPluginVersion }} enableMicrobuildForMacAndLinux: ${{ parameters.enableMicrobuildForMacAndLinux }} continueOnError: ${{ parameters.continueOnError }} diff --git a/eng/common/core-templates/job/publish-build-assets.yml b/eng/common/core-templates/job/publish-build-assets.yml index 06f2eed0323..700f7711465 100644 --- a/eng/common/core-templates/job/publish-build-assets.yml +++ b/eng/common/core-templates/job/publish-build-assets.yml @@ -91,8 +91,8 @@ jobs: fetchDepth: 3 clean: true - - ${{ if eq(parameters.isAssetlessBuild, 'false') }}: - - ${{ if eq(parameters.publishingVersion, 3) }}: + - ${{ if eq(parameters.isAssetlessBuild, 'false') }}: + - ${{ if eq(parameters.publishingVersion, 3) }}: - task: DownloadPipelineArtifact@2 displayName: Download Asset Manifests inputs: @@ -117,7 +117,7 @@ jobs: flattenFolders: true condition: ${{ parameters.condition }} continueOnError: ${{ parameters.continueOnError }} - + - task: NuGetAuthenticate@1 # Populate internal runtime variables. @@ -125,7 +125,7 @@ jobs: ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: parameters: legacyCredential: $(dn-bot-dnceng-artifact-feeds-rw) - + - template: /eng/common/templates/steps/enable-internal-runtimes.yml - task: AzureCLI@2 @@ -145,7 +145,7 @@ jobs: condition: ${{ parameters.condition }} continueOnError: ${{ parameters.continueOnError }} - + - task: powershell@2 displayName: Create ReleaseConfigs Artifact inputs: @@ -191,7 +191,7 @@ jobs: BARBuildId: ${{ parameters.BARBuildId }} PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} is1ESPipeline: ${{ parameters.is1ESPipeline }} - + # Darc is targeting 8.0, so make sure it's installed - task: UseDotNet@2 inputs: diff --git a/eng/common/core-templates/job/renovate.yml b/eng/common/core-templates/job/renovate.yml new file mode 100644 index 00000000000..ff86c80b468 --- /dev/null +++ b/eng/common/core-templates/job/renovate.yml @@ -0,0 +1,196 @@ +# -------------------------------------------------------------------------------------- +# Renovate Bot Job Template +# -------------------------------------------------------------------------------------- +# This Azure DevOps pipeline job template runs Renovate (https://docs.renovatebot.com/) +# to automatically update dependencies in a GitHub repository. +# +# Renovate scans the repository for dependency files and creates pull requests to update +# outdated dependencies based on the configuration specified in the renovateConfigPath +# parameter. +# +# Usage: +# For each product repo wanting to make use of Renovate, this template is called from +# an internal Azure DevOps pipeline, typically with a schedule trigger, to check for +# and propose dependency updates. +# +# For more info, see https://github.com/dotnet/arcade/blob/main/Documentation/Renovate.md +# -------------------------------------------------------------------------------------- + +parameters: + +# Path to the Renovate configuration file within the repository. +- name: renovateConfigPath + type: string + default: 'eng/renovate.json' + +# GitHub repository to run Renovate against, in the format 'owner/repo'. +# This could technically be any repo but convention is to target the same +# repo that contains the calling pipeline. The Renovate config file would +# be co-located with the pipeline's repo and, in most cases, the config +# file is specific to the repo being targeted. +- name: gitHubRepo + type: string + +# List of base branches to target for Renovate PRs. +# NOTE: The Renovate configuration file is always read from the branch where the +# pipeline is run, NOT from the target branches specified here. If you need different +# configurations for different branches, run the pipeline from each branch separately. +- name: baseBranches + type: object + default: + - main + +# When true, Renovate will run in dry run mode, which previews changes without creating PRs. +# See the 'Run Renovate' step log output for details of what would have been changed. +- name: dryRun + type: boolean + default: false + +# By default, Renovate will not recreate a PR for a given dependency/version pair that was +# previously closed. This allows opting in to always recreating PRs even if they were +# previously closed. +- name: forceRecreatePR + type: boolean + default: false + +# Name of the arcade repository resource in the pipeline. +# This allows repos which haven't been onboarded to Arcade to still use this +# template by checking out the repo as a resource with a custom name and pointing +# this parameter to it. +- name: arcadeRepoResource + type: string + default: self + +# Directory name for the self repo under $(Build.SourcesDirectory) in multi-checkout. +# In multi-checkout (when arcadeRepoResource != 'self'), Azure DevOps checks out the +# self repo to $(Build.SourcesDirectory)/. Set this to match the auto-generated +# directory name. Using the auto-generated name is necessary rather than explicitly +# defining a checkout path because container jobs expect repos to live under the agent's +# workspace ($(Pipeline.Workspace)). On some self-hosted setups the host path +# (e.g., /mnt/vss/_work) differs from the container path (e.g., /__w), and a custom checkout +# path can fail validation. Using the default checkout location keeps the paths consistent +# and avoids this issue. +- name: selfRepoName + type: string + default: '' +- name: arcadeRepoName + type: string + default: '' + +# Pool configuration for the job. +- name: pool + type: object + default: + name: NetCore1ESPool-Internal + image: build.azurelinux.3.amd64 + os: linux + +jobs: +- job: Renovate + displayName: Run Renovate + container: RenovateContainer + variables: + - group: dotnet-renovate-bot + # The Renovate version is automatically updated by https://github.com/dotnet/arcade/blob/main/azure-pipelines-renovate.yml. + # Changing the variable name here would require updating the name in https://github.com/dotnet/arcade/blob/main/eng/renovate.json as well. + - name: renovateVersion + value: '42' + readonly: true + - name: renovateLogFilePath + value: '$(Build.ArtifactStagingDirectory)/renovate.json' + readonly: true + - name: dryRunArg + readonly: true + ${{ if eq(parameters.dryRun, true) }}: + value: 'full' + ${{ else }}: + value: '' + - name: recreateWhenArg + readonly: true + ${{ if eq(parameters.forceRecreatePR, true) }}: + value: 'always' + ${{ else }}: + value: '' + # In multi-checkout (without custom paths), Azure DevOps places each repo under + # $(Build.SourcesDirectory)/. selfRepoName must be provided in that case. + - name: selfRepoPath + readonly: true + ${{ if eq(parameters.arcadeRepoResource, 'self') }}: + value: '$(Build.SourcesDirectory)' + ${{ else }}: + value: '$(Build.SourcesDirectory)/${{ parameters.selfRepoName }}' + - name: arcadeRepoPath + readonly: true + ${{ if eq(parameters.arcadeRepoResource, 'self') }}: + value: '$(Build.SourcesDirectory)' + ${{ else }}: + value: '$(Build.SourcesDirectory)/${{ parameters.arcadeRepoName }}' + pool: ${{ parameters.pool }} + + templateContext: + outputParentDirectory: $(Build.ArtifactStagingDirectory) + outputs: + - output: pipelineArtifact + displayName: Publish Renovate Log + condition: succeededOrFailed() + targetPath: $(Build.ArtifactStagingDirectory) + artifactName: $(Agent.JobName)_Logs_Attempt$(System.JobAttempt) + isProduction: false # logs are non-production artifacts + + steps: + - checkout: self + fetchDepth: 1 + + - ${{ if ne(parameters.arcadeRepoResource, 'self') }}: + - checkout: ${{ parameters.arcadeRepoResource }} + fetchDepth: 1 + + - script: | + renovate-config-validator $(selfRepoPath)/${{parameters.renovateConfigPath}} 2>&1 | tee /tmp/renovate-config-validator.out + validatorExit=${PIPESTATUS[0]} + if grep -q '^ WARN:' /tmp/renovate-config-validator.out; then + echo "##vso[task.logissue type=warning]Renovate config validator produced warnings." + echo "##vso[task.complete result=SucceededWithIssues]" + fi + exit $validatorExit + displayName: Validate Renovate config + env: + LOG_LEVEL: info + LOG_FILE_LEVEL: debug + LOG_FILE: $(Build.ArtifactStagingDirectory)/renovate-config-validator.json + + - script: | + . $(arcadeRepoPath)/eng/common/renovate.env + renovate 2>&1 | tee /tmp/renovate.out + renovateExit=${PIPESTATUS[0]} + if grep -q '^ WARN:' /tmp/renovate.out; then + echo "##vso[task.logissue type=warning]Renovate produced warnings." + echo "##vso[task.complete result=SucceededWithIssues]" + fi + exit $renovateExit + displayName: Run Renovate + env: + RENOVATE_FORK_TOKEN: $(BotAccount-dotnet-renovate-bot-PAT) + RENOVATE_TOKEN: $(BotAccount-dotnet-renovate-bot-PAT) + RENOVATE_REPOSITORIES: ${{parameters.gitHubRepo}} + RENOVATE_BASE_BRANCHES: ${{ convertToJson(parameters.baseBranches) }} + RENOVATE_DRY_RUN: $(dryRunArg) + RENOVATE_RECREATE_WHEN: $(recreateWhenArg) + LOG_LEVEL: info + LOG_FILE_LEVEL: debug + LOG_FILE: $(renovateLogFilePath) + RENOVATE_CONFIG_FILE: $(selfRepoPath)/${{parameters.renovateConfigPath}} + + - script: | + echo "PRs created by Renovate:" + if [ -s "$(renovateLogFilePath)" ]; then + if ! jq -r 'select(.msg == "PR created" and .pr != null) | "https://github.com/\(.repository)/pull/\(.pr)"' "$(renovateLogFilePath)" | sort -u; then + echo "##vso[task.logissue type=warning]Failed to parse Renovate log file with jq." + echo "##vso[task.complete result=SucceededWithIssues]" + fi + else + echo "##vso[task.logissue type=warning]No Renovate log file found or file is empty." + echo "##vso[task.complete result=SucceededWithIssues]" + fi + displayName: List created PRs + condition: and(succeededOrFailed(), eq('${{ parameters.dryRun }}', false)) diff --git a/eng/common/core-templates/job/source-index-stage1.yml b/eng/common/core-templates/job/source-index-stage1.yml index 76baf5c2725..bac6ac5faac 100644 --- a/eng/common/core-templates/job/source-index-stage1.yml +++ b/eng/common/core-templates/job/source-index-stage1.yml @@ -15,6 +15,8 @@ jobs: variables: - name: BinlogPath value: ${{ parameters.binlogPath }} + - name: skipComponentGovernanceDetection + value: true - template: /eng/common/core-templates/variables/pool-providers.yml parameters: is1ESPipeline: ${{ parameters.is1ESPipeline }} @@ -25,10 +27,10 @@ jobs: pool: ${{ if eq(variables['System.TeamProject'], 'public') }}: name: $(DncEngPublicBuildPool) - image: windows.vs2026preview.scout.amd64.open + image: windows.vs2026.amd64.open ${{ if eq(variables['System.TeamProject'], 'internal') }}: name: $(DncEngInternalBuildPool) - image: windows.vs2026preview.scout.amd64 + image: windows.vs2026.amd64 steps: - ${{ if eq(parameters.is1ESPipeline, '') }}: diff --git a/eng/common/core-templates/post-build/common-variables.yml b/eng/common/core-templates/post-build/common-variables.yml index d5627a994ae..db298ae16ba 100644 --- a/eng/common/core-templates/post-build/common-variables.yml +++ b/eng/common/core-templates/post-build/common-variables.yml @@ -11,8 +11,6 @@ variables: - name: MaestroApiVersion value: "2020-02-20" - - name: SourceLinkCLIVersion - value: 3.0.0 - name: SymbolToolVersion value: 1.0.1 - name: BinlogToolVersion diff --git a/eng/common/core-templates/post-build/post-build.yml b/eng/common/core-templates/post-build/post-build.yml index 905a6315e2d..fcf40d1d2e6 100644 --- a/eng/common/core-templates/post-build/post-build.yml +++ b/eng/common/core-templates/post-build/post-build.yml @@ -1,118 +1,108 @@ parameters: - # Which publishing infra should be used. THIS SHOULD MATCH THE VERSION ON THE BUILD MANIFEST. - # Publishing V1 is no longer supported - # Publishing V2 is no longer supported - # Publishing V3 is the default - - name: publishingInfraVersion - displayName: Which version of publishing should be used to promote the build definition? - type: number - default: 3 - values: - - 3 - - 4 - - - name: BARBuildId - displayName: BAR Build Id - type: number - default: 0 - - - name: PromoteToChannelIds - displayName: Channel to promote BARBuildId to - type: string - default: '' - - - name: enableSourceLinkValidation - displayName: Enable SourceLink validation - type: boolean - default: false - - - name: enableSigningValidation - displayName: Enable signing validation - type: boolean - default: true - - - name: enableSymbolValidation - displayName: Enable symbol validation - type: boolean - default: false - - - name: enableNugetValidation - displayName: Enable NuGet validation - type: boolean - default: true - - - name: publishInstallersAndChecksums - displayName: Publish installers and checksums - type: boolean - default: true - - - name: requireDefaultChannels - displayName: Fail the build if there are no default channel(s) registrations for the current build - type: boolean - default: false - - - name: SDLValidationParameters - type: object - default: - enable: false - publishGdn: false - continueOnError: false - params: '' - artifactNames: '' - downloadArtifacts: true - - - name: isAssetlessBuild - type: boolean - displayName: Is Assetless Build - default: false - - # These parameters let the user customize the call to sdk-task.ps1 for publishing - # symbols & general artifacts as well as for signing validation - - name: symbolPublishingAdditionalParameters - displayName: Symbol publishing additional parameters - type: string - default: '' - - - name: artifactsPublishingAdditionalParameters - displayName: Artifact publishing additional parameters - type: string - default: '' - - - name: signingValidationAdditionalParameters - displayName: Signing validation additional parameters - type: string - default: '' - - # Which stages should finish execution before post-build stages start - - name: validateDependsOn - type: object - default: - - build - - - name: publishDependsOn - type: object - default: - - Validate - - # Optional: Call asset publishing rather than running in a separate stage - - name: publishAssetsImmediately - type: boolean - default: false - - - name: is1ESPipeline - type: boolean - default: false +# Which publishing infra should be used. THIS SHOULD MATCH THE VERSION ON THE BUILD MANIFEST. +# Publishing V1 is no longer supported +# Publishing V2 is no longer supported +# Publishing V3 is the default +- name: publishingInfraVersion + displayName: Which version of publishing should be used to promote the build definition? + type: number + default: 3 + values: + - 3 + - 4 + +- name: BARBuildId + displayName: BAR Build Id + type: number + default: 0 + +- name: PromoteToChannelIds + displayName: Channel to promote BARBuildId to + type: string + default: '' + +- name: enableSourceLinkValidation + displayName: Enable SourceLink validation + type: boolean + default: false + +- name: enableSigningValidation + displayName: Enable signing validation + type: boolean + default: true + +- name: enableSymbolValidation + displayName: Enable symbol validation + type: boolean + default: false + +- name: enableNugetValidation + displayName: Enable NuGet validation + type: boolean + default: true + +- name: publishInstallersAndChecksums + displayName: Publish installers and checksums + type: boolean + default: true + +- name: requireDefaultChannels + displayName: Fail the build if there are no default channel(s) registrations for the current build + type: boolean + default: false + +- name: isAssetlessBuild + type: boolean + displayName: Is Assetless Build + default: false + +# These parameters let the user customize the call to sdk-task.ps1 for publishing +# symbols & general artifacts as well as for signing validation +- name: symbolPublishingAdditionalParameters + displayName: Symbol publishing additional parameters + type: string + default: '' + +- name: artifactsPublishingAdditionalParameters + displayName: Artifact publishing additional parameters + type: string + default: '' + +- name: signingValidationAdditionalParameters + displayName: Signing validation additional parameters + type: string + default: '' + +# Which stages should finish execution before post-build stages start +- name: validateDependsOn + type: object + default: + - build + +- name: publishDependsOn + type: object + default: + - Validate + +# Optional: Call asset publishing rather than running in a separate stage +- name: publishAssetsImmediately + type: boolean + default: false + +- name: is1ESPipeline + type: boolean + default: false stages: -- ${{ if or(eq( parameters.enableNugetValidation, 'true'), eq(parameters.enableSigningValidation, 'true'), eq(parameters.enableSourceLinkValidation, 'true'), eq(parameters.SDLValidationParameters.enable, 'true')) }}: +- ${{ if or(eq( parameters.enableNugetValidation, 'true'), eq(parameters.enableSigningValidation, 'true'), eq(parameters.enableSourceLinkValidation, 'true')) }}: - stage: Validate dependsOn: ${{ parameters.validateDependsOn }} displayName: Validate Build Assets variables: - - template: /eng/common/core-templates/post-build/common-variables.yml - - template: /eng/common/core-templates/variables/pool-providers.yml - parameters: - is1ESPipeline: ${{ parameters.is1ESPipeline }} + - template: /eng/common/core-templates/post-build/common-variables.yml + - template: /eng/common/core-templates/variables/pool-providers.yml + parameters: + is1ESPipeline: ${{ parameters.is1ESPipeline }} jobs: - job: displayName: NuGet Validation @@ -128,49 +118,49 @@ stages: ${{ else }}: ${{ if eq(parameters.is1ESPipeline, true) }}: name: $(DncEngInternalBuildPool) - image: windows.vs2026preview.scout.amd64 + image: windows.vs2026.amd64 os: windows ${{ else }}: name: $(DncEngInternalBuildPool) - demands: ImageOverride -equals windows.vs2026preview.scout.amd64 + demands: ImageOverride -equals windows.vs2026.amd64 steps: - - template: /eng/common/core-templates/post-build/setup-maestro-vars.yml - parameters: - BARBuildId: ${{ parameters.BARBuildId }} - PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} - is1ESPipeline: ${{ parameters.is1ESPipeline }} - - - ${{ if ne(parameters.publishingInfraVersion, 4) }}: - - task: DownloadBuildArtifacts@0 - displayName: Download Package Artifacts - inputs: - buildType: specific - buildVersionToDownload: specific - project: $(AzDOProjectName) - pipeline: $(AzDOPipelineId) - buildId: $(AzDOBuildId) - artifactName: PackageArtifacts - checkDownloadedFiles: true - - ${{ if eq(parameters.publishingInfraVersion, 4) }}: - - task: DownloadPipelineArtifact@2 - displayName: Download Pipeline Artifacts (V4) - inputs: - itemPattern: '*/packages/**/*.nupkg' - targetPath: '$(Build.ArtifactStagingDirectory)/PipelineArtifactsDownload' - - task: CopyFiles@2 - displayName: Flatten packages to PackageArtifacts - inputs: - SourceFolder: '$(Build.ArtifactStagingDirectory)/PipelineArtifactsDownload' - Contents: '**/*.nupkg' - TargetFolder: '$(Build.ArtifactStagingDirectory)/PackageArtifacts' - flattenFolders: true - - - task: PowerShell@2 - displayName: Validate + - template: /eng/common/core-templates/post-build/setup-maestro-vars.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + is1ESPipeline: ${{ parameters.is1ESPipeline }} + + - ${{ if ne(parameters.publishingInfraVersion, 4) }}: + - task: DownloadBuildArtifacts@0 + displayName: Download Package Artifacts + inputs: + buildType: specific + buildVersionToDownload: specific + project: $(AzDOProjectName) + pipeline: $(AzDOPipelineId) + buildId: $(AzDOBuildId) + artifactName: PackageArtifacts + checkDownloadedFiles: true + - ${{ if eq(parameters.publishingInfraVersion, 4) }}: + - task: DownloadPipelineArtifact@2 + displayName: Download Pipeline Artifacts (V4) + inputs: + itemPattern: '*/packages/**/*.nupkg' + targetPath: '$(Build.ArtifactStagingDirectory)/PipelineArtifactsDownload' + - task: CopyFiles@2 + displayName: Flatten packages to PackageArtifacts inputs: - filePath: $(System.DefaultWorkingDirectory)/eng/common/post-build/nuget-validation.ps1 - arguments: -PackagesPath $(Build.ArtifactStagingDirectory)/PackageArtifacts/ + SourceFolder: '$(Build.ArtifactStagingDirectory)/PipelineArtifactsDownload' + Contents: '**/*.nupkg' + TargetFolder: '$(Build.ArtifactStagingDirectory)/PackageArtifacts' + flattenFolders: true + + - task: PowerShell@2 + displayName: Validate + inputs: + filePath: $(System.DefaultWorkingDirectory)/eng/common/post-build/nuget-validation.ps1 + arguments: -PackagesPath $(Build.ArtifactStagingDirectory)/PackageArtifacts/ - job: displayName: Signing Validation @@ -184,143 +174,96 @@ stages: os: windows # If it's not devdiv, it's dnceng ${{ else }}: - ${{ if eq(parameters.is1ESPipeline, true) }}: + ${{ if eq(parameters.is1ESPipeline, true) }}: name: $(DncEngInternalBuildPool) image: windows.vs2026.amd64 os: windows ${{ else }}: name: $(DncEngInternalBuildPool) - demands: ImageOverride -equals windows.vs2026preview.scout.amd64 + demands: ImageOverride -equals windows.vs2026.amd64 steps: - - template: /eng/common/core-templates/post-build/setup-maestro-vars.yml - parameters: - BARBuildId: ${{ parameters.BARBuildId }} - PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} - is1ESPipeline: ${{ parameters.is1ESPipeline }} - - - ${{ if ne(parameters.publishingInfraVersion, 4) }}: - - task: DownloadBuildArtifacts@0 - displayName: Download Package Artifacts - inputs: - buildType: specific - buildVersionToDownload: specific - project: $(AzDOProjectName) - pipeline: $(AzDOPipelineId) - buildId: $(AzDOBuildId) - artifactName: PackageArtifacts - checkDownloadedFiles: true - - ${{ if eq(parameters.publishingInfraVersion, 4) }}: - - task: DownloadPipelineArtifact@2 - displayName: Download Pipeline Artifacts (V4) - inputs: - itemPattern: '*/packages/**/*.nupkg' - targetPath: '$(Build.ArtifactStagingDirectory)/PipelineArtifactsDownload' - - task: CopyFiles@2 - displayName: Flatten packages to PackageArtifacts - inputs: - SourceFolder: '$(Build.ArtifactStagingDirectory)/PipelineArtifactsDownload' - Contents: '**/*.nupkg' - TargetFolder: '$(Build.ArtifactStagingDirectory)/PackageArtifacts' - flattenFolders: true - - # This is necessary whenever we want to publish/restore to an AzDO private feed - # Since sdk-task.ps1 tries to restore packages we need to do this authentication here - # otherwise it'll complain about accessing a private feed. - - task: NuGetAuthenticate@1 - displayName: 'Authenticate to AzDO Feeds' - - # Signing validation will optionally work with the buildmanifest file which is downloaded from - # Azure DevOps above. - - task: PowerShell@2 - displayName: Validate - inputs: - filePath: eng\common\sdk-task.ps1 - arguments: -task SigningValidation -restore -msbuildEngine vs - /p:PackageBasePath='$(Build.ArtifactStagingDirectory)/PackageArtifacts' - /p:SignCheckExclusionsFile='$(System.DefaultWorkingDirectory)/eng/SignCheckExclusionsFile.txt' - ${{ parameters.signingValidationAdditionalParameters }} - - - template: /eng/common/core-templates/steps/publish-logs.yml - parameters: - is1ESPipeline: ${{ parameters.is1ESPipeline }} - StageLabel: 'Validation' - JobLabel: 'Signing' - BinlogToolVersion: $(BinlogToolVersion) + - template: /eng/common/core-templates/post-build/setup-maestro-vars.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + is1ESPipeline: ${{ parameters.is1ESPipeline }} - - job: - displayName: SourceLink Validation - condition: eq( ${{ parameters.enableSourceLinkValidation }}, 'true') - pool: - # We don't use the collection uri here because it might vary (.visualstudio.com vs. dev.azure.com) - ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: - name: AzurePipelines-EO - image: 1ESPT-Windows2025 - demands: Cmd - os: windows - # If it's not devdiv, it's dnceng - ${{ else }}: - ${{ if eq(parameters.is1ESPipeline, true) }}: - name: $(DncEngInternalBuildPool) - image: windows.vs2026.amd64 - os: windows - ${{ else }}: - name: $(DncEngInternalBuildPool) - demands: ImageOverride -equals windows.vs2026preview.scout.amd64 - steps: - - template: /eng/common/core-templates/post-build/setup-maestro-vars.yml - parameters: - BARBuildId: ${{ parameters.BARBuildId }} - PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} - is1ESPipeline: ${{ parameters.is1ESPipeline }} - - - ${{ if ne(parameters.publishingInfraVersion, 4) }}: - - task: DownloadBuildArtifacts@0 - displayName: Download Blob Artifacts - inputs: - buildType: specific - buildVersionToDownload: specific - project: $(AzDOProjectName) - pipeline: $(AzDOPipelineId) - buildId: $(AzDOBuildId) - artifactName: BlobArtifacts - checkDownloadedFiles: true - - ${{ if eq(parameters.publishingInfraVersion, 4) }}: - - task: DownloadPipelineArtifact@2 - displayName: Download Pipeline Artifacts (V4) - inputs: - itemPattern: '*/assets/**' - targetPath: '$(Build.ArtifactStagingDirectory)/PipelineArtifactsDownload' - - task: CopyFiles@2 - displayName: Flatten assets to BlobArtifacts - inputs: - SourceFolder: '$(Build.ArtifactStagingDirectory)/PipelineArtifactsDownload' - Contents: '**/*' - TargetFolder: '$(Build.ArtifactStagingDirectory)/BlobArtifacts' - flattenFolders: true - - - task: PowerShell@2 - displayName: Validate + - ${{ if ne(parameters.publishingInfraVersion, 4) }}: + - task: DownloadBuildArtifacts@0 + displayName: Download Package Artifacts + inputs: + buildType: specific + buildVersionToDownload: specific + project: $(AzDOProjectName) + pipeline: $(AzDOPipelineId) + buildId: $(AzDOBuildId) + artifactName: PackageArtifacts + checkDownloadedFiles: true + - ${{ if eq(parameters.publishingInfraVersion, 4) }}: + - task: DownloadPipelineArtifact@2 + displayName: Download Pipeline Artifacts (V4) + inputs: + itemPattern: '*/packages/**/*.nupkg' + targetPath: '$(Build.ArtifactStagingDirectory)/PipelineArtifactsDownload' + - task: CopyFiles@2 + displayName: Flatten packages to PackageArtifacts + inputs: + SourceFolder: '$(Build.ArtifactStagingDirectory)/PipelineArtifactsDownload' + Contents: '**/*.nupkg' + TargetFolder: '$(Build.ArtifactStagingDirectory)/PackageArtifacts' + flattenFolders: true + + # This is necessary whenever we want to publish/restore to an AzDO private feed + # Since sdk-task.ps1 tries to restore packages we need to do this authentication here + # otherwise it'll complain about accessing a private feed. + - task: NuGetAuthenticate@1 + displayName: 'Authenticate to AzDO Feeds' + + # Signing validation will optionally work with the buildmanifest file which is downloaded from + # Azure DevOps above. + - task: PowerShell@2 + displayName: Validate + inputs: + filePath: eng\common\sdk-task.ps1 + arguments: -task SigningValidation -restore + /p:PackageBasePath='$(Build.ArtifactStagingDirectory)/PackageArtifacts' + /p:SignCheckExclusionsFile='$(System.DefaultWorkingDirectory)/eng/SignCheckExclusionsFile.txt' + ${{ parameters.signingValidationAdditionalParameters }} + + - template: /eng/common/core-templates/steps/publish-logs.yml + parameters: + is1ESPipeline: ${{ parameters.is1ESPipeline }} + StageLabel: 'Validation' + JobLabel: 'Signing' + BinlogToolVersion: $(BinlogToolVersion) + + # SourceLink validation has been removed — the underlying CLI tool + # (targeting netcoreapp2.1) has not functioned for years. + # The enableSourceLinkValidation parameter is kept but ignored so + # existing pipelines that pass it are not broken. + # See https://github.com/dotnet/arcade/issues/16647 + - ${{ if eq(parameters.enableSourceLinkValidation, 'true') }}: + - job: + displayName: 'SourceLink Validation Removed - please remove enableSourceLinkValidation from your pipeline' + pool: server + steps: + - task: Delay@1 + displayName: 'Warning: SourceLink validation removed (see https://github.com/dotnet/arcade/issues/16647)' inputs: - filePath: $(System.DefaultWorkingDirectory)/eng/common/post-build/sourcelink-validation.ps1 - arguments: -InputPath $(Build.ArtifactStagingDirectory)/BlobArtifacts/ - -ExtractPath $(Agent.BuildDirectory)/Extract/ - -GHRepoName $(Build.Repository.Name) - -GHCommit $(Build.SourceVersion) - -SourcelinkCliVersion $(SourceLinkCLIVersion) - continueOnError: true + delayForMinutes: '0' - ${{ if ne(parameters.publishAssetsImmediately, 'true') }}: - stage: publish_using_darc - ${{ if or(eq(parameters.enableNugetValidation, 'true'), eq(parameters.enableSigningValidation, 'true'), eq(parameters.enableSourceLinkValidation, 'true'), eq(parameters.SDLValidationParameters.enable, 'true')) }}: + ${{ if or(eq(parameters.enableNugetValidation, 'true'), eq(parameters.enableSigningValidation, 'true'), eq(parameters.enableSourceLinkValidation, 'true')) }}: dependsOn: ${{ parameters.publishDependsOn }} ${{ else }}: dependsOn: ${{ parameters.validateDependsOn }} displayName: Publish using Darc variables: - - template: /eng/common/core-templates/post-build/common-variables.yml - - template: /eng/common/core-templates/variables/pool-providers.yml - parameters: - is1ESPipeline: ${{ parameters.is1ESPipeline }} + - template: /eng/common/core-templates/post-build/common-variables.yml + - template: /eng/common/core-templates/variables/pool-providers.yml + parameters: + is1ESPipeline: ${{ parameters.is1ESPipeline }} jobs: - job: displayName: Publish Using Darc @@ -334,7 +277,7 @@ stages: os: windows # If it's not devdiv, it's dnceng ${{ else }}: - ${{ if eq(parameters.is1ESPipeline, true) }}: + ${{ if eq(parameters.is1ESPipeline, true) }}: name: NetCore1ESPool-Publishing-Internal image: windows.vs2026.amd64 os: windows @@ -342,34 +285,33 @@ stages: name: NetCore1ESPool-Publishing-Internal demands: ImageOverride -equals windows.vs2026.amd64 steps: - - template: /eng/common/core-templates/post-build/setup-maestro-vars.yml - parameters: - BARBuildId: ${{ parameters.BARBuildId }} - PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} - is1ESPipeline: ${{ parameters.is1ESPipeline }} - - - task: NuGetAuthenticate@1 - - # Populate internal runtime variables. - - template: /eng/common/templates/steps/enable-internal-sources.yml - parameters: - legacyCredential: $(dn-bot-dnceng-artifact-feeds-rw) - - - template: /eng/common/templates/steps/enable-internal-runtimes.yml + - template: /eng/common/core-templates/post-build/setup-maestro-vars.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + is1ESPipeline: ${{ parameters.is1ESPipeline }} - # Darc is targeting 8.0, so make sure it's installed - - task: UseDotNet@2 - inputs: - version: 8.0.x + - task: NuGetAuthenticate@1 - - task: AzureCLI@2 - displayName: Publish Using Darc - inputs: - azureSubscription: "Darc: Maestro Production" - scriptType: ps - scriptLocation: scriptPath - scriptPath: $(System.DefaultWorkingDirectory)/eng/common/post-build/publish-using-darc.ps1 - arguments: > + # Populate internal runtime variables. + - template: /eng/common/templates/steps/enable-internal-sources.yml + parameters: + legacyCredential: $(dn-bot-dnceng-artifact-feeds-rw) + + - template: /eng/common/templates/steps/enable-internal-runtimes.yml + + - task: UseDotNet@2 + inputs: + version: 8.0.x + + - task: AzureCLI@2 + displayName: Publish Using Darc + inputs: + azureSubscription: "Darc: Maestro Production" + scriptType: ps + scriptLocation: scriptPath + scriptPath: $(System.DefaultWorkingDirectory)/eng/common/post-build/publish-using-darc.ps1 + arguments: > -BuildId $(BARBuildId) -PublishingInfraVersion 3 -AzdoToken '$(System.AccessToken)' diff --git a/eng/common/core-templates/stages/renovate.yml b/eng/common/core-templates/stages/renovate.yml new file mode 100644 index 00000000000..edab2818258 --- /dev/null +++ b/eng/common/core-templates/stages/renovate.yml @@ -0,0 +1,111 @@ +# -------------------------------------------------------------------------------------- +# Renovate Pipeline Template +# -------------------------------------------------------------------------------------- +# This template provides a complete reusable pipeline definition for running Renovate +# in a 1ES Official pipeline. Pipelines can extend from this template and only need +# to pass the Renovate job parameters. +# +# For more info, see https://github.com/dotnet/arcade/blob/main/Documentation/Renovate.md +# -------------------------------------------------------------------------------------- + +parameters: + +# Path to the Renovate configuration file within the repository. +- name: renovateConfigPath + type: string + default: 'eng/renovate.json' + +# GitHub repository to run Renovate against, in the format 'owner/repo'. +- name: gitHubRepo + type: string + +# List of base branches to target for Renovate PRs. +- name: baseBranches + type: object + default: + - main + +# When true, Renovate will run in dry run mode. +- name: dryRun + type: boolean + default: false + +# When true, Renovate will recreate PRs even if they were previously closed. +- name: forceRecreatePR + type: boolean + default: false + +# Name of the arcade repository resource in the pipeline. +# This allows repos which haven't been onboarded to Arcade to still use this +# template by checking out the repo as a resource with a custom name and pointing +# this parameter to it. +- name: arcadeRepoResource + type: string + default: 'self' + +- name: selfRepoName + type: string + default: '' +- name: arcadeRepoName + type: string + default: '' + +# Pool configuration for the pipeline. +- name: pool + type: object + default: + name: NetCore1ESPool-Internal + image: build.azurelinux.3.amd64 + os: linux + +# Renovate version used in the container image tag. +- name: renovateVersion + default: 43 + type: number + +# Pool configuration for SDL analysis. +- name: sdlPool + type: object + default: + name: NetCore1ESPool-Internal + image: windows.vs2026.amd64 + os: windows + +resources: + repositories: + - repository: 1ESPipelineTemplates + type: git + name: 1ESPipelineTemplates/1ESPipelineTemplates + ref: refs/tags/release + +extends: + template: v1/1ES.Official.PipelineTemplate.yml@1ESPipelineTemplates + parameters: + pool: ${{ parameters.pool }} + sdl: + sourceAnalysisPool: ${{ parameters.sdlPool }} + # When repos that aren't onboarded to Arcade use this template, they set the + # arcadeRepoResource parameter to point to their Arcade repo resource. In that case, + # Aracde will be excluded from SDL analysis. + ${{ if ne(parameters.arcadeRepoResource, 'self') }}: + sourceRepositoriesToScan: + exclude: + - repository: ${{ parameters.arcadeRepoResource }} + containers: + RenovateContainer: + image: mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-renovate-${{ parameters.renovateVersion }}-amd64 + stages: + - stage: Renovate + displayName: Run Renovate + jobs: + - template: /eng/common/core-templates/job/renovate.yml@${{ parameters.arcadeRepoResource }} + parameters: + renovateConfigPath: ${{ parameters.renovateConfigPath }} + gitHubRepo: ${{ parameters.gitHubRepo }} + baseBranches: ${{ parameters.baseBranches }} + dryRun: ${{ parameters.dryRun }} + forceRecreatePR: ${{ parameters.forceRecreatePR }} + pool: ${{ parameters.pool }} + arcadeRepoResource: ${{ parameters.arcadeRepoResource }} + selfRepoName: ${{ parameters.selfRepoName }} + arcadeRepoName: ${{ parameters.arcadeRepoName }} diff --git a/eng/common/core-templates/steps/install-microbuild-impl.yml b/eng/common/core-templates/steps/install-microbuild-impl.yml new file mode 100644 index 00000000000..da22beb3f60 --- /dev/null +++ b/eng/common/core-templates/steps/install-microbuild-impl.yml @@ -0,0 +1,34 @@ +parameters: + - name: microbuildTaskInputs + type: object + default: {} + + - name: microbuildEnv + type: object + default: {} + + - name: enablePreviewMicrobuild + type: boolean + default: false + + - name: condition + type: string + + - name: continueOnError + type: boolean + +steps: +- ${{ if eq(parameters.enablePreviewMicrobuild, true) }}: + - task: MicroBuildSigningPluginPreview@4 + displayName: Install Preview MicroBuild plugin + inputs: ${{ parameters.microbuildTaskInputs }} + env: ${{ parameters.microbuildEnv }} + continueOnError: ${{ parameters.continueOnError }} + condition: ${{ parameters.condition }} +- ${{ else }}: + - task: MicroBuildSigningPlugin@4 + displayName: Install MicroBuild plugin + inputs: ${{ parameters.microbuildTaskInputs }} + env: ${{ parameters.microbuildEnv }} + continueOnError: ${{ parameters.continueOnError }} + condition: ${{ parameters.condition }} diff --git a/eng/common/core-templates/steps/install-microbuild.yml b/eng/common/core-templates/steps/install-microbuild.yml index 553fce66b94..76a54e157fd 100644 --- a/eng/common/core-templates/steps/install-microbuild.yml +++ b/eng/common/core-templates/steps/install-microbuild.yml @@ -4,6 +4,8 @@ parameters: # Enable install tasks for MicroBuild on Mac and Linux # Will be ignored if 'enableMicrobuild' is false or 'Agent.Os' is 'Windows_NT' enableMicrobuildForMacAndLinux: false + # Enable preview version of MB signing plugin + enablePreviewMicrobuild: false # Determines whether the ESRP service connection information should be passed to the signing plugin. # This overlaps with _SignType to some degree. We only need the service connection for real signing. # It's important that the service connection not be passed to the MicroBuildSigningPlugin task in this place. @@ -13,6 +15,8 @@ parameters: microbuildUseESRP: true # Microbuild installation directory microBuildOutputFolder: $(Agent.TempDirectory)/MicroBuild + # Microbuild version + microbuildPluginVersion: 'latest' continueOnError: false @@ -69,42 +73,46 @@ steps: # YAML expansion, and Windows vs. Linux/Mac uses different service connections. However, # we can avoid including the MB install step if not enabled at all. This avoids a bunch of # extra pipeline authorizations, since most pipelines do not sign on non-Windows. - - task: MicroBuildSigningPlugin@4 - displayName: Install MicroBuild plugin (Windows) - inputs: - signType: $(_SignType) - zipSources: false - feedSource: https://dnceng.pkgs.visualstudio.com/_packaging/MicroBuildToolset/nuget/v3/index.json - ${{ if eq(parameters.microbuildUseESRP, true) }}: - ConnectedServiceName: 'MicroBuild Signing Task (DevDiv)' - ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: - ConnectedPMEServiceName: 6cc74545-d7b9-4050-9dfa-ebefcc8961ea - ${{ else }}: - ConnectedPMEServiceName: 248d384a-b39b-46e3-8ad5-c2c210d5e7ca - env: - TeamName: $(_TeamName) - MicroBuildOutputFolderOverride: ${{ parameters.microBuildOutputFolder }} - SYSTEM_ACCESSTOKEN: $(System.AccessToken) - continueOnError: ${{ parameters.continueOnError }} - condition: and(succeeded(), eq(variables['Agent.Os'], 'Windows_NT'), in(variables['_SignType'], 'real', 'test')) - - - ${{ if eq(parameters.enableMicrobuildForMacAndLinux, true) }}: - - task: MicroBuildSigningPlugin@4 - displayName: Install MicroBuild plugin (non-Windows) - inputs: + - template: /eng/common/core-templates/steps/install-microbuild-impl.yml + parameters: + enablePreviewMicrobuild: ${{ parameters.enablePreviewMicrobuild }} + microbuildTaskInputs: signType: $(_SignType) zipSources: false feedSource: https://dnceng.pkgs.visualstudio.com/_packaging/MicroBuildToolset/nuget/v3/index.json - workingDirectory: ${{ parameters.microBuildOutputFolder }} + version: ${{ parameters.microbuildPluginVersion }} ${{ if eq(parameters.microbuildUseESRP, true) }}: ConnectedServiceName: 'MicroBuild Signing Task (DevDiv)' ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: - ConnectedPMEServiceName: beb8cb23-b303-4c95-ab26-9e44bc958d39 + ConnectedPMEServiceName: 6cc74545-d7b9-4050-9dfa-ebefcc8961ea ${{ else }}: - ConnectedPMEServiceName: c24de2a5-cc7a-493d-95e4-8e5ff5cad2bc - env: + ConnectedPMEServiceName: 248d384a-b39b-46e3-8ad5-c2c210d5e7ca + microbuildEnv: TeamName: $(_TeamName) MicroBuildOutputFolderOverride: ${{ parameters.microBuildOutputFolder }} SYSTEM_ACCESSTOKEN: $(System.AccessToken) continueOnError: ${{ parameters.continueOnError }} - condition: and(succeeded(), ne(variables['Agent.Os'], 'Windows_NT'), eq(variables['_SignType'], 'real')) + condition: and(succeeded(), eq(variables['Agent.Os'], 'Windows_NT'), in(variables['_SignType'], 'real', 'test')) + + - ${{ if eq(parameters.enableMicrobuildForMacAndLinux, true) }}: + - template: /eng/common/core-templates/steps/install-microbuild-impl.yml + parameters: + enablePreviewMicrobuild: ${{ parameters.enablePreviewMicrobuild }} + microbuildTaskInputs: + signType: $(_SignType) + zipSources: false + feedSource: https://dnceng.pkgs.visualstudio.com/_packaging/MicroBuildToolset/nuget/v3/index.json + version: ${{ parameters.microbuildPluginVersion }} + workingDirectory: ${{ parameters.microBuildOutputFolder }} + ${{ if eq(parameters.microbuildUseESRP, true) }}: + ConnectedServiceName: 'MicroBuild Signing Task (DevDiv)' + ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: + ConnectedPMEServiceName: beb8cb23-b303-4c95-ab26-9e44bc958d39 + ${{ else }}: + ConnectedPMEServiceName: c24de2a5-cc7a-493d-95e4-8e5ff5cad2bc + microbuildEnv: + TeamName: $(_TeamName) + MicroBuildOutputFolderOverride: ${{ parameters.microBuildOutputFolder }} + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + continueOnError: ${{ parameters.continueOnError }} + condition: and(succeeded(), ne(variables['Agent.Os'], 'Windows_NT'), eq(variables['_SignType'], 'real')) diff --git a/eng/common/core-templates/steps/publish-logs.yml b/eng/common/core-templates/steps/publish-logs.yml index 4eed0312b80..84a1922c73f 100644 --- a/eng/common/core-templates/steps/publish-logs.yml +++ b/eng/common/core-templates/steps/publish-logs.yml @@ -61,3 +61,4 @@ steps: condition: always() retryCountOnTaskFailure: 10 # for any files being locked isProduction: false # logs are non-production artifacts + diff --git a/eng/common/core-templates/steps/source-build.yml b/eng/common/core-templates/steps/source-build.yml index 09ae5cd73ae..b75f59c428d 100644 --- a/eng/common/core-templates/steps/source-build.yml +++ b/eng/common/core-templates/steps/source-build.yml @@ -24,7 +24,7 @@ steps: # in the default public locations. internalRuntimeDownloadArgs= if [ '$(dotnetbuilds-internal-container-read-token-base64)' != '$''(dotnetbuilds-internal-container-read-token-base64)' ]; then - internalRuntimeDownloadArgs='/p:DotNetRuntimeSourceFeed=https://ci.dot.net/internal /p:DotNetRuntimeSourceFeedKey=$(dotnetbuilds-internal-container-read-token-base64) --runtimesourcefeed https://ci.dot.net/internal --runtimesourcefeedkey '$(dotnetbuilds-internal-container-read-token-base64)'' + internalRuntimeDownloadArgs='/p:DotNetRuntimeSourceFeed=https://ci.dot.net/internal /p:DotNetRuntimeSourceFeedKey=$(dotnetbuilds-internal-container-read-token-base64) --runtimesourcefeed https://ci.dot.net/internal --runtimesourcefeedkey $(dotnetbuilds-internal-container-read-token-base64)' fi buildConfig=Release diff --git a/eng/common/core-templates/steps/source-index-stage1-publish.yml b/eng/common/core-templates/steps/source-index-stage1-publish.yml index e9a694afa58..3ad83b8c307 100644 --- a/eng/common/core-templates/steps/source-index-stage1-publish.yml +++ b/eng/common/core-templates/steps/source-index-stage1-publish.yml @@ -1,6 +1,6 @@ parameters: - sourceIndexUploadPackageVersion: 2.0.0-20250818.1 - sourceIndexProcessBinlogPackageVersion: 1.0.1-20250818.1 + sourceIndexUploadPackageVersion: 2.0.0-20250906.1 + sourceIndexProcessBinlogPackageVersion: 1.0.1-20250906.1 sourceIndexPackageSource: https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json binlogPath: artifacts/log/Debug/Build.binlog @@ -14,8 +14,8 @@ steps: workingDirectory: $(Agent.TempDirectory) - script: | - $(Agent.TempDirectory)/dotnet/dotnet tool install BinLogToSln --version ${{parameters.sourceIndexProcessBinlogPackageVersion}} --add-source ${{parameters.SourceIndexPackageSource}} --tool-path $(Agent.TempDirectory)/.source-index/tools - $(Agent.TempDirectory)/dotnet/dotnet tool install UploadIndexStage1 --version ${{parameters.sourceIndexUploadPackageVersion}} --add-source ${{parameters.SourceIndexPackageSource}} --tool-path $(Agent.TempDirectory)/.source-index/tools + $(Agent.TempDirectory)/dotnet/dotnet tool install BinLogToSln --version ${{parameters.sourceIndexProcessBinlogPackageVersion}} --source ${{parameters.sourceIndexPackageSource}} --tool-path $(Agent.TempDirectory)/.source-index/tools + $(Agent.TempDirectory)/dotnet/dotnet tool install UploadIndexStage1 --version ${{parameters.sourceIndexUploadPackageVersion}} --source ${{parameters.sourceIndexPackageSource}} --tool-path $(Agent.TempDirectory)/.source-index/tools displayName: "Source Index: Download netsourceindex Tools" # Set working directory to temp directory so 'dotnet' doesn't try to use global.json and use the repo's sdk. workingDirectory: $(Agent.TempDirectory) diff --git a/eng/common/cross/build-rootfs.sh b/eng/common/cross/build-rootfs.sh index 8abfb71f727..314c93c5759 100755 --- a/eng/common/cross/build-rootfs.sh +++ b/eng/common/cross/build-rootfs.sh @@ -9,6 +9,7 @@ usage() echo "CodeName - optional, Code name for Linux, can be: xenial(default), zesty, bionic, alpine" echo " for alpine can be specified with version: alpineX.YY or alpineedge" echo " for FreeBSD can be: freebsd13, freebsd14" + echo " for OpenBSD can be: openbsd" echo " for illumos can be: illumos" echo " for Haiku can be: haiku." echo "lldbx.y - optional, LLDB version, can be: lldb3.9(default), lldb4.0, lldb5.0, lldb6.0 no-lldb. Ignored for alpine and FreeBSD" @@ -27,6 +28,8 @@ __BuildArch=arm __AlpineArch=armv7 __FreeBSDArch=arm __FreeBSDMachineArch=armv7 +__OpenBSDArch=arm +__OpenBSDMachineArch=armv7 __IllumosArch=arm7 __HaikuArch=arm __QEMUArch=arm @@ -72,7 +75,7 @@ __AlpinePackages+=" krb5-dev" __AlpinePackages+=" openssl-dev" __AlpinePackages+=" zlib-dev" -__FreeBSDBase="13.4-RELEASE" +__FreeBSDBase="13.5-RELEASE" __FreeBSDPkg="1.21.3" __FreeBSDABI="13" __FreeBSDPackages="libunwind" @@ -82,6 +85,12 @@ __FreeBSDPackages+=" openssl" __FreeBSDPackages+=" krb5" __FreeBSDPackages+=" terminfo-db" +__OpenBSDVersion="7.8" +__OpenBSDPackages="heimdal-libs" +__OpenBSDPackages+=" icu4c" +__OpenBSDPackages+=" inotify-tools" +__OpenBSDPackages+=" openssl" + __IllumosPackages="icu" __IllumosPackages+=" mit-krb5" __IllumosPackages+=" openssl" @@ -160,6 +169,8 @@ while :; do __QEMUArch=aarch64 __FreeBSDArch=arm64 __FreeBSDMachineArch=aarch64 + __OpenBSDArch=arm64 + __OpenBSDMachineArch=aarch64 ;; armel) __BuildArch=armel @@ -235,6 +246,8 @@ while :; do __UbuntuArch=amd64 __FreeBSDArch=amd64 __FreeBSDMachineArch=amd64 + __OpenBSDArch=amd64 + __OpenBSDMachineArch=amd64 __illumosArch=x86_64 __HaikuArch=x86_64 __UbuntuRepo="http://archive.ubuntu.com/ubuntu/" @@ -295,9 +308,7 @@ while :; do ;; noble) # Ubuntu 24.04 __CodeName=noble - if [[ -z "$__LLDB_Package" ]]; then - __LLDB_Package="liblldb-19-dev" - fi + __LLDB_Package="liblldb-19-dev" ;; stretch) # Debian 9 __CodeName=stretch @@ -383,10 +394,14 @@ while :; do ;; freebsd14) __CodeName=freebsd - __FreeBSDBase="14.2-RELEASE" + __FreeBSDBase="14.3-RELEASE" __FreeBSDABI="14" __SkipUnmount=1 ;; + openbsd) + __CodeName=openbsd + __SkipUnmount=1 + ;; illumos) __CodeName=illumos __SkipUnmount=1 @@ -595,6 +610,62 @@ elif [[ "$__CodeName" == "freebsd" ]]; then INSTALL_AS_USER=$(whoami) "$__RootfsDir"/host/sbin/pkg -r "$__RootfsDir" -C "$__RootfsDir"/usr/local/etc/pkg.conf update # shellcheck disable=SC2086 INSTALL_AS_USER=$(whoami) "$__RootfsDir"/host/sbin/pkg -r "$__RootfsDir" -C "$__RootfsDir"/usr/local/etc/pkg.conf install --yes $__FreeBSDPackages +elif [[ "$__CodeName" == "openbsd" ]]; then + # determine mirrors + OPENBSD_MIRROR="https://cdn.openbsd.org/pub/OpenBSD/$__OpenBSDVersion/$__OpenBSDMachineArch" + + # download base system sets + ensureDownloadTool + + BASE_SETS=(base comp) + for set in "${BASE_SETS[@]}"; do + FILE="${set}${__OpenBSDVersion//./}.tgz" + echo "Downloading $FILE..." + if [[ "$__hasWget" == 1 ]]; then + wget -O- "$OPENBSD_MIRROR/$FILE" | tar -C "$__RootfsDir" -xzpf - + else + curl -SL "$OPENBSD_MIRROR/$FILE" | tar -C "$__RootfsDir" -xzpf - + fi + done + + PKG_MIRROR="https://cdn.openbsd.org/pub/OpenBSD/${__OpenBSDVersion}/packages/${__OpenBSDMachineArch}" + + echo "Installing packages into sysroot..." + + # Fetch package index once + if [[ "$__hasWget" == 1 ]]; then + PKG_INDEX=$(wget -qO- "$PKG_MIRROR/") + else + PKG_INDEX=$(curl -s "$PKG_MIRROR/") + fi + + for pkg in $__OpenBSDPackages; do + PKG_FILE=$(echo "$PKG_INDEX" | grep -Po ">\K${pkg}-[0-9][^\" ]*\.tgz" \ + | sort -V | tail -n1) + + echo "Resolved package filename for $pkg: $PKG_FILE" + + [[ -z "$PKG_FILE" ]] && { echo "ERROR: Package $pkg not found"; exit 1; } + + if [[ "$__hasWget" == 1 ]]; then + wget -O- "$PKG_MIRROR/$PKG_FILE" | tar -C "$__RootfsDir" -xzpf - + else + curl -SL "$PKG_MIRROR/$PKG_FILE" | tar -C "$__RootfsDir" -xzpf - + fi + done + + echo "Creating versionless symlinks for shared libraries..." + # Find all versioned .so files and create the base .so symlink + for lib in "$__RootfsDir/usr/lib/libc++.so."* "$__RootfsDir/usr/lib/libc++abi.so."* "$__RootfsDir/usr/lib/libpthread.so."*; do + if [ -f "$lib" ]; then + # Extract the filename (e.g., libc++.so.12.0) + VERSIONED_NAME=$(basename "$lib") + # Remove the trailing version numbers (e.g., libc++.so) + BASE_NAME=${VERSIONED_NAME%.so.*}.so + # Create the symlink in the same directory + ln -sf "$VERSIONED_NAME" "$__RootfsDir/usr/lib/$BASE_NAME" + fi + done elif [[ "$__CodeName" == "illumos" ]]; then mkdir "$__RootfsDir/tmp" pushd "$__RootfsDir/tmp" diff --git a/eng/common/cross/toolchain.cmake b/eng/common/cross/toolchain.cmake index 0ff85cf0367..ff2dfdb4a5b 100644 --- a/eng/common/cross/toolchain.cmake +++ b/eng/common/cross/toolchain.cmake @@ -3,15 +3,22 @@ set(CROSS_ROOTFS $ENV{ROOTFS_DIR}) # reset platform variables (e.g. cmake 3.25 sets LINUX=1) unset(LINUX) unset(FREEBSD) +unset(OPENBSD) unset(ILLUMOS) unset(ANDROID) unset(TIZEN) unset(HAIKU) set(TARGET_ARCH_NAME $ENV{TARGET_BUILD_ARCH}) + +file(GLOB OPENBSD_PROBE "${CROSS_ROOTFS}/etc/signify/openbsd-*.pub") + if(EXISTS ${CROSS_ROOTFS}/bin/freebsd-version) set(CMAKE_SYSTEM_NAME FreeBSD) set(FREEBSD 1) +elseif(OPENBSD_PROBE) + set(CMAKE_SYSTEM_NAME OpenBSD) + set(OPENBSD 1) elseif(EXISTS ${CROSS_ROOTFS}/usr/platform/i86pc) set(CMAKE_SYSTEM_NAME SunOS) set(ILLUMOS 1) @@ -53,6 +60,8 @@ elseif(TARGET_ARCH_NAME STREQUAL "arm64") endif() elseif(FREEBSD) set(triple "aarch64-unknown-freebsd12") + elseif(OPENBSD) + set(triple "aarch64-unknown-openbsd") endif() elseif(TARGET_ARCH_NAME STREQUAL "armel") set(CMAKE_SYSTEM_PROCESSOR armv7l) @@ -109,6 +118,8 @@ elseif(TARGET_ARCH_NAME STREQUAL "x64") endif() elseif(FREEBSD) set(triple "x86_64-unknown-freebsd12") + elseif(OPENBSD) + set(triple "x86_64-unknown-openbsd") elseif(ILLUMOS) set(TOOLCHAIN "x86_64-illumos") elseif(HAIKU) @@ -193,7 +204,7 @@ if(ANDROID) # include official NDK toolchain script include(${CROSS_ROOTFS}/../build/cmake/android.toolchain.cmake) -elseif(FREEBSD) +elseif(FREEBSD OR OPENBSD) # we cross-compile by instructing clang set(CMAKE_C_COMPILER_TARGET ${triple}) set(CMAKE_CXX_COMPILER_TARGET ${triple}) @@ -291,7 +302,7 @@ endif() # Specify compile options -if((TARGET_ARCH_NAME MATCHES "^(arm|arm64|armel|armv6|loongarch64|ppc64le|riscv64|s390x|x64|x86)$" AND NOT ANDROID AND NOT FREEBSD) OR ILLUMOS OR HAIKU) +if((TARGET_ARCH_NAME MATCHES "^(arm|arm64|armel|armv6|loongarch64|ppc64le|riscv64|s390x|x64|x86)$" AND NOT ANDROID AND NOT FREEBSD AND NOT OPENBSD) OR ILLUMOS OR HAIKU) set(CMAKE_C_COMPILER_TARGET ${TOOLCHAIN}) set(CMAKE_CXX_COMPILER_TARGET ${TOOLCHAIN}) set(CMAKE_ASM_COMPILER_TARGET ${TOOLCHAIN}) diff --git a/eng/common/darc-init.ps1 b/eng/common/darc-init.ps1 index e3374310563..a5be41db690 100644 --- a/eng/common/darc-init.ps1 +++ b/eng/common/darc-init.ps1 @@ -29,11 +29,11 @@ function InstallDarcCli ($darcVersion, $toolpath) { Write-Host "Installing Darc CLI version $darcVersion..." Write-Host 'You may need to restart your command window if this is the first dotnet tool you have installed.' if (-not $toolpath) { - Write-Host "'$dotnet' tool install $darcCliPackageName --version $darcVersion --add-source '$arcadeServicesSource' -v $verbosity -g" - & "$dotnet" tool install $darcCliPackageName --version $darcVersion --add-source "$arcadeServicesSource" -v $verbosity -g + Write-Host "'$dotnet' tool install $darcCliPackageName --version $darcVersion --source '$arcadeServicesSource' -v $verbosity -g" + & "$dotnet" tool install $darcCliPackageName --version $darcVersion --source "$arcadeServicesSource" -v $verbosity -g }else { - Write-Host "'$dotnet' tool install $darcCliPackageName --version $darcVersion --add-source '$arcadeServicesSource' -v $verbosity --tool-path '$toolpath'" - & "$dotnet" tool install $darcCliPackageName --version $darcVersion --add-source "$arcadeServicesSource" -v $verbosity --tool-path "$toolpath" + Write-Host "'$dotnet' tool install $darcCliPackageName --version $darcVersion --source '$arcadeServicesSource' -v $verbosity --tool-path '$toolpath'" + & "$dotnet" tool install $darcCliPackageName --version $darcVersion --source "$arcadeServicesSource" -v $verbosity --tool-path "$toolpath" } } diff --git a/eng/common/darc-init.sh b/eng/common/darc-init.sh index e889f439b8d..b56d40e5706 100755 --- a/eng/common/darc-init.sh +++ b/eng/common/darc-init.sh @@ -5,7 +5,7 @@ darcVersion='' versionEndpoint='https://maestro.dot.net/api/assets/darc-version?api-version=2020-02-20' verbosity='minimal' -while [[ $# > 0 ]]; do +while [[ $# -gt 0 ]]; do opt="$(echo "$1" | tr "[:upper:]" "[:lower:]")" case "$opt" in --darcversion) @@ -73,9 +73,9 @@ function InstallDarcCli { echo "Installing Darc CLI version $darcVersion..." echo "You may need to restart your command shell if this is the first dotnet tool you have installed." if [ -z "$toolpath" ]; then - echo $($dotnet_root/dotnet tool install $darc_cli_package_name --version $darcVersion --add-source "$arcadeServicesSource" -v $verbosity -g) + echo $($dotnet_root/dotnet tool install $darc_cli_package_name --version $darcVersion --source "$arcadeServicesSource" -v $verbosity -g) else - echo $($dotnet_root/dotnet tool install $darc_cli_package_name --version $darcVersion --add-source "$arcadeServicesSource" -v $verbosity --tool-path "$toolpath") + echo $($dotnet_root/dotnet tool install $darc_cli_package_name --version $darcVersion --source "$arcadeServicesSource" -v $verbosity --tool-path "$toolpath") fi } diff --git a/eng/common/dotnet-install.sh b/eng/common/dotnet-install.sh index 7b9d97e3bd4..61f302bb677 100755 --- a/eng/common/dotnet-install.sh +++ b/eng/common/dotnet-install.sh @@ -18,7 +18,7 @@ architecture='' runtime='dotnet' runtimeSourceFeed='' runtimeSourceFeedKey='' -while [[ $# > 0 ]]; do +while [[ $# -gt 0 ]]; do opt="$(echo "$1" | tr "[:upper:]" "[:lower:]")" case "$opt" in -version|-v) diff --git a/eng/common/dotnet.sh b/eng/common/dotnet.sh index 2ef68235675..f6d24871c1d 100755 --- a/eng/common/dotnet.sh +++ b/eng/common/dotnet.sh @@ -19,7 +19,7 @@ source $scriptroot/tools.sh InitializeDotNetCli true # install # Invoke acquired SDK with args if they are provided -if [[ $# > 0 ]]; then +if [[ $# -gt 0 ]]; then __dotnetDir=${_InitializeDotNetCli} dotnetPath=${__dotnetDir}/dotnet ${dotnetPath} "$@" diff --git a/eng/common/internal-feed-operations.sh b/eng/common/internal-feed-operations.sh index 9378223ba09..6299e7effd4 100755 --- a/eng/common/internal-feed-operations.sh +++ b/eng/common/internal-feed-operations.sh @@ -100,7 +100,7 @@ operation='' authToken='' repoName='' -while [[ $# > 0 ]]; do +while [[ $# -gt 0 ]]; do opt="$(echo "$1" | tr "[:upper:]" "[:lower:]")" case "$opt" in --operation) diff --git a/eng/common/native/init-distro-rid.sh b/eng/common/native/init-distro-rid.sh index 83ea7aab0e0..8fc6d2fec78 100644 --- a/eng/common/native/init-distro-rid.sh +++ b/eng/common/native/init-distro-rid.sh @@ -39,6 +39,8 @@ getNonPortableDistroRid() # $rootfsDir can be empty. freebsd-version is a shell script and should always work. __freebsd_major_version=$("$rootfsDir"/bin/freebsd-version | cut -d'.' -f1) nonPortableRid="freebsd.$__freebsd_major_version-${targetArch}" + elif [ "$targetOs" = "openbsd" ]; then + nonPortableRid="openbsd.$(uname -r)-${targetArch}" elif command -v getprop >/dev/null && getprop ro.product.system.model | grep -qi android; then __android_sdk_version=$(getprop ro.build.version.sdk) nonPortableRid="android.$__android_sdk_version-${targetArch}" diff --git a/eng/common/native/install-dependencies.sh b/eng/common/native/install-dependencies.sh index 477a44f335b..4742177a768 100644 --- a/eng/common/native/install-dependencies.sh +++ b/eng/common/native/install-dependencies.sh @@ -24,14 +24,16 @@ case "$os" in apt update apt install -y build-essential gettext locales cmake llvm clang lld lldb liblldb-dev libunwind8-dev libicu-dev liblttng-ust-dev \ - libssl-dev libkrb5-dev pigz cpio + libssl-dev libkrb5-dev pigz cpio ninja-build localedef -i en_US -c -f UTF-8 -A /usr/share/locale/locale.alias en_US.UTF-8 - elif [ "$ID" = "fedora" ] || [ "$ID" = "rhel" ] || [ "$ID" = "azurelinux" ]; then + elif [ "$ID" = "fedora" ] || [ "$ID" = "rhel" ] || [ "$ID" = "azurelinux" ] || [ "$ID" = "centos" ]; then pkg_mgr="$(command -v tdnf 2>/dev/null || command -v dnf)" - $pkg_mgr install -y cmake llvm lld lldb clang python curl libicu-devel openssl-devel krb5-devel lttng-ust-devel pigz cpio + $pkg_mgr install -y cmake llvm lld lldb clang python curl libicu-devel openssl-devel krb5-devel lttng-ust-devel pigz cpio ninja-build + elif [ "$ID" = "amzn" ]; then + dnf install -y cmake llvm lld lldb clang python libicu-devel openssl-devel krb5-devel lttng-ust-devel pigz cpio ninja-build elif [ "$ID" = "alpine" ]; then - apk add build-base cmake bash curl clang llvm-dev lld lldb krb5-dev lttng-ust-dev icu-dev openssl-dev pigz cpio + apk add build-base cmake bash curl clang llvm llvm-dev lld lldb-dev krb5-dev lttng-ust-dev icu-dev openssl-dev pigz cpio ninja else echo "Unsupported distro. distro: $ID" exit 1 @@ -52,6 +54,7 @@ brew "openssl@3" brew "pkgconf" brew "python3" brew "pigz" +brew "ninja" EOF ;; diff --git a/eng/common/post-build/redact-logs.ps1 b/eng/common/post-build/redact-logs.ps1 index 472d5bb562c..672f4e2652e 100644 --- a/eng/common/post-build/redact-logs.ps1 +++ b/eng/common/post-build/redact-logs.ps1 @@ -9,7 +9,8 @@ param( [Parameter(Mandatory=$false)][string] $TokensFilePath, [Parameter(ValueFromRemainingArguments=$true)][String[]]$TokensToRedact, [Parameter(Mandatory=$false)][string] $runtimeSourceFeed, - [Parameter(Mandatory=$false)][string] $runtimeSourceFeedKey) + [Parameter(Mandatory=$false)][string] $runtimeSourceFeedKey +) try { $ErrorActionPreference = 'Stop' @@ -48,8 +49,8 @@ try { Write-Host "Installing Binlog redactor CLI..." Write-Host "'$dotnet' new tool-manifest" & "$dotnet" new tool-manifest - Write-Host "'$dotnet' tool install $packageName --local --add-source '$PackageFeed' -v $verbosity --version $BinlogToolVersion" - & "$dotnet" tool install $packageName --local --add-source "$PackageFeed" -v $verbosity --version $BinlogToolVersion + Write-Host "'$dotnet' tool install $packageName --local --source '$PackageFeed' -v $verbosity --version $BinlogToolVersion" + & "$dotnet" tool install $packageName --local --source "$PackageFeed" -v $verbosity --version $BinlogToolVersion if (Test-Path $TokensFilePath) { Write-Host "Adding additional sensitive data for redaction from file: " $TokensFilePath diff --git a/eng/common/renovate.env b/eng/common/renovate.env new file mode 100644 index 00000000000..17ecc05d9b1 --- /dev/null +++ b/eng/common/renovate.env @@ -0,0 +1,42 @@ +# Renovate Global Configuration +# https://docs.renovatebot.com/self-hosted-configuration/ +# +# NOTE: This file uses bash/shell format and is sourced via `. renovate.env`. +# Values containing spaces or special characters must be quoted. + +# Author to use for git commits made by Renovate +# https://docs.renovatebot.com/configuration-options/#gitauthor +export RENOVATE_GIT_AUTHOR='.NET Renovate ' + +# Disable rate limiting for PR creation (0 = unlimited) +# https://docs.renovatebot.com/presets-default/#prhourlylimitnone +# https://docs.renovatebot.com/presets-default/#prconcurrentlimitnone +export RENOVATE_PR_HOURLY_LIMIT=0 +export RENOVATE_PR_CONCURRENT_LIMIT=0 + +# Skip the onboarding PR that Renovate normally creates for new repos +# https://docs.renovatebot.com/config-overview/#onboarding +export RENOVATE_ONBOARDING=false + +# Any Renovate config file in the cloned repository is ignored. Only +# the Renovate config file from the repo where the pipeline is running +# is used (yes, those are the same repo but the sources may be different). +# https://docs.renovatebot.com/self-hosted-configuration/#requireconfig +export RENOVATE_REQUIRE_CONFIG=ignored + +# Customize the PR body content. This removes some of the default +# sections that aren't relevant in a self-hosted config. +# https://docs.renovatebot.com/configuration-options/#prheader +# https://docs.renovatebot.com/configuration-options/#prbodynotes +# https://docs.renovatebot.com/configuration-options/#prbodytemplate +export RENOVATE_PR_HEADER='## Automated Dependency Update' +export RENOVATE_PR_BODY_NOTES='["This PR has been created automatically by the [.NET Renovate Bot](https://github.com/dotnet/arcade/blob/main/Documentation/Renovate.md) to update one or more dependencies in your repo. Please review the changes and merge the PR if everything looks good."]' +export RENOVATE_PR_BODY_TEMPLATE='{{{header}}}{{{table}}}{{{warnings}}}{{{notes}}}{{{changelogs}}}' + +# Extend the global config with additional presets +# https://docs.renovatebot.com/self-hosted-configuration/#globalextends +# Disable the Dependency Dashboard issue that tracks all updates +export RENOVATE_GLOBAL_EXTENDS='[":disableDependencyDashboard"]' + +# Allow all commands for post-upgrade commands. +export RENOVATE_ALLOWED_COMMANDS='[".*"]' diff --git a/eng/common/sdk-task.ps1 b/eng/common/sdk-task.ps1 index b64b66a6275..64fd2f8abec 100644 --- a/eng/common/sdk-task.ps1 +++ b/eng/common/sdk-task.ps1 @@ -66,20 +66,7 @@ try { if( $msbuildEngine -eq "vs") { # Ensure desktop MSBuild is available for sdk tasks. - if( -not ($GlobalJson.tools.PSObject.Properties.Name -contains "vs" )) { - $GlobalJson.tools | Add-Member -Name "vs" -Value (ConvertFrom-Json "{ `"version`": `"16.5`" }") -MemberType NoteProperty - } - if( -not ($GlobalJson.tools.PSObject.Properties.Name -match "xcopy-msbuild" )) { - $GlobalJson.tools | Add-Member -Name "xcopy-msbuild" -Value "18.0.0" -MemberType NoteProperty - } - if ($GlobalJson.tools."xcopy-msbuild".Trim() -ine "none") { - $xcopyMSBuildToolsFolder = InitializeXCopyMSBuild $GlobalJson.tools."xcopy-msbuild" -install $true - } - if ($xcopyMSBuildToolsFolder -eq $null) { - throw 'Unable to get xcopy downloadable version of msbuild' - } - - $global:_MSBuildExe = "$($xcopyMSBuildToolsFolder)\MSBuild\Current\Bin\MSBuild.exe" + $global:_MSBuildExe = InitializeVisualStudioMSBuild } $taskProject = GetSdkTaskProject $task diff --git a/eng/common/template-guidance.md b/eng/common/template-guidance.md index e2b07a865f1..f772aa3d78f 100644 --- a/eng/common/template-guidance.md +++ b/eng/common/template-guidance.md @@ -71,7 +71,6 @@ eng\common\ source-build.yml (shim) source-index-stage1.yml (shim) jobs\ - codeql-build.yml (shim) jobs.yml (shim) source-build.yml (shim) post-build\ @@ -88,7 +87,6 @@ eng\common\ source-build.yml (shim) variables\ pool-providers.yml (logic + redirect) # templates/variables/pool-providers.yml will redirect to templates-official/variables/pool-providers.yml if you are running in the internal project - sdl-variables.yml (logic) core-templates\ job\ job.yml (logic) @@ -97,7 +95,6 @@ eng\common\ source-build.yml (logic) source-index-stage1.yml (logic) jobs\ - codeql-build.yml (logic) jobs.yml (logic) source-build.yml (logic) post-build\ diff --git a/eng/common/tools.ps1 b/eng/common/tools.ps1 index 977a2d4b103..e28db6c7c8f 100644 --- a/eng/common/tools.ps1 +++ b/eng/common/tools.ps1 @@ -34,6 +34,9 @@ # Configures warning treatment in msbuild. [bool]$warnAsError = if (Test-Path variable:warnAsError) { $warnAsError } else { $true } +# Specifies semi-colon delimited list of warning codes that should not be treated as errors. +[string]$warnNotAsError = if (Test-Path variable:warnNotAsError) { $warnNotAsError } else { '' } + # Specifies which msbuild engine to use for build: 'vs', 'dotnet' or unspecified (determined based on presence of tools.vs in global.json). [string]$msbuildEngine = if (Test-Path variable:msbuildEngine) { $msbuildEngine } else { $null } @@ -157,9 +160,6 @@ function InitializeDotNetCli([bool]$install, [bool]$createSdkLocationFile) { return $global:_DotNetInstallDir } - # Don't resolve runtime, shared framework, or SDK from other locations to ensure build determinism - $env:DOTNET_MULTILEVEL_LOOKUP=0 - # Disable first run since we do not need all ASP.NET packages restored. $env:DOTNET_NOLOGO=1 @@ -185,7 +185,11 @@ function InitializeDotNetCli([bool]$install, [bool]$createSdkLocationFile) { if ((-not $globalJsonHasRuntimes) -and (-not [string]::IsNullOrEmpty($env:DOTNET_INSTALL_DIR)) -and (Test-Path(Join-Path $env:DOTNET_INSTALL_DIR "sdk\$dotnetSdkVersion"))) { $dotnetRoot = $env:DOTNET_INSTALL_DIR } else { - $dotnetRoot = Join-Path $RepoRoot '.dotnet' + if (-not [string]::IsNullOrEmpty($env:DOTNET_GLOBAL_INSTALL_DIR)) { + $dotnetRoot = $env:DOTNET_GLOBAL_INSTALL_DIR + } else { + $dotnetRoot = Join-Path $RepoRoot '.dotnet' + } if (-not (Test-Path(Join-Path $dotnetRoot "sdk\$dotnetSdkVersion"))) { if ($install) { @@ -225,7 +229,6 @@ function InitializeDotNetCli([bool]$install, [bool]$createSdkLocationFile) { # Make Sure that our bootstrapped dotnet cli is available in future steps of the Azure Pipelines build Write-PipelinePrependPath -Path $dotnetRoot - Write-PipelineSetVariable -Name 'DOTNET_MULTILEVEL_LOOKUP' -Value '0' Write-PipelineSetVariable -Name 'DOTNET_NOLOGO' -Value '1' return $global:_DotNetInstallDir = $dotnetRoot @@ -299,6 +302,8 @@ function InstallDotNet([string] $dotnetRoot, $dotnetVersionLabel = "'sdk v$version'" + # For performance this check is duplicated in src/Microsoft.DotNet.Arcade.Sdk/src/InstallDotNetCore.cs + # if you are making changes here, consider if you need to make changes there as well. if ($runtime -ne '' -and $runtime -ne 'sdk') { $runtimePath = $dotnetRoot $runtimePath = $runtimePath + "\shared" @@ -374,12 +379,11 @@ function InstallDotNet([string] $dotnetRoot, # # 1. MSBuild from an active VS command prompt # 2. MSBuild from a compatible VS installation -# 3. MSBuild from the xcopy tool package # # Returns full path to msbuild.exe. # Throws on failure. # -function InitializeVisualStudioMSBuild([bool]$install, [object]$vsRequirements = $null) { +function InitializeVisualStudioMSBuild([object]$vsRequirements = $null) { if (-not (IsWindowsPlatform)) { throw "Cannot initialize Visual Studio on non-Windows" } @@ -389,13 +393,7 @@ function InitializeVisualStudioMSBuild([bool]$install, [object]$vsRequirements = } # Minimum VS version to require. - $vsMinVersionReqdStr = '17.7' - $vsMinVersionReqd = [Version]::new($vsMinVersionReqdStr) - - # If the version of msbuild is going to be xcopied, - # use this version. Version matches a package here: - # https://dev.azure.com/dnceng/public/_artifacts/feed/dotnet-eng/NuGet/Microsoft.DotNet.Arcade.MSBuild.Xcopy/versions/18.0.0 - $defaultXCopyMSBuildVersion = '18.0.0' + $vsMinVersionReqdStr = '18.0' if (!$vsRequirements) { if (Get-Member -InputObject $GlobalJson.tools -Name 'vs') { @@ -425,46 +423,16 @@ function InitializeVisualStudioMSBuild([bool]$install, [object]$vsRequirements = } } - # Locate Visual Studio installation or download x-copy msbuild. + # Locate Visual Studio installation. $vsInfo = LocateVisualStudio $vsRequirements - if ($vsInfo -ne $null -and $env:ForceUseXCopyMSBuild -eq $null) { + if ($vsInfo -ne $null) { # Ensure vsInstallDir has a trailing slash $vsInstallDir = Join-Path $vsInfo.installationPath "\" $vsMajorVersion = $vsInfo.installationVersion.Split('.')[0] InitializeVisualStudioEnvironmentVariables $vsInstallDir $vsMajorVersion } else { - if (Get-Member -InputObject $GlobalJson.tools -Name 'xcopy-msbuild') { - $xcopyMSBuildVersion = $GlobalJson.tools.'xcopy-msbuild' - $vsMajorVersion = $xcopyMSBuildVersion.Split('.')[0] - } else { - #if vs version provided in global.json is incompatible (too low) then use the default version for xcopy msbuild download - if($vsMinVersion -lt $vsMinVersionReqd){ - Write-Host "Using xcopy-msbuild version of $defaultXCopyMSBuildVersion since VS version $vsMinVersionStr provided in global.json is not compatible" - $xcopyMSBuildVersion = $defaultXCopyMSBuildVersion - $vsMajorVersion = $xcopyMSBuildVersion.Split('.')[0] - } - else{ - # If the VS version IS compatible, look for an xcopy msbuild package - # with a version matching VS. - # Note: If this version does not exist, then an explicit version of xcopy msbuild - # can be specified in global.json. This will be required for pre-release versions of msbuild. - $vsMajorVersion = $vsMinVersion.Major - $vsMinorVersion = $vsMinVersion.Minor - $xcopyMSBuildVersion = "$vsMajorVersion.$vsMinorVersion.0" - } - } - - $vsInstallDir = $null - if ($xcopyMSBuildVersion.Trim() -ine "none") { - $vsInstallDir = InitializeXCopyMSBuild $xcopyMSBuildVersion $install - if ($vsInstallDir -eq $null) { - throw "Could not xcopy msbuild. Please check that package 'Microsoft.DotNet.Arcade.MSBuild.Xcopy @ $xcopyMSBuildVersion' exists on feed 'dotnet-eng'." - } - } - if ($vsInstallDir -eq $null) { - throw 'Unable to find Visual Studio that has required version and components installed' - } + throw 'Unable to find Visual Studio that has required version and components installed' } $msbuildVersionDir = if ([int]$vsMajorVersion -lt 16) { "$vsMajorVersion.0" } else { "Current" } @@ -491,38 +459,6 @@ function InitializeVisualStudioEnvironmentVariables([string] $vsInstallDir, [str } } -function InstallXCopyMSBuild([string]$packageVersion) { - return InitializeXCopyMSBuild $packageVersion -install $true -} - -function InitializeXCopyMSBuild([string]$packageVersion, [bool]$install) { - $packageName = 'Microsoft.DotNet.Arcade.MSBuild.Xcopy' - $packageDir = Join-Path $ToolsDir "msbuild\$packageVersion" - $packagePath = Join-Path $packageDir "$packageName.$packageVersion.nupkg" - - if (!(Test-Path $packageDir)) { - if (!$install) { - return $null - } - - Create-Directory $packageDir - - Write-Host "Downloading $packageName $packageVersion" - $ProgressPreference = 'SilentlyContinue' # Don't display the console progress UI - it's a huge perf hit - Retry({ - Invoke-WebRequest "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/flat2/$packageName/$packageVersion/$packageName.$packageVersion.nupkg" -UseBasicParsing -OutFile $packagePath - }) - - if (!(Test-Path $packagePath)) { - Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "See https://dev.azure.com/dnceng/internal/_wiki/wikis/DNCEng%20Services%20Wiki/1074/Updating-Microsoft.DotNet.Arcade.MSBuild.Xcopy-WAS-RoslynTools.MSBuild-(xcopy-msbuild)-generation?anchor=troubleshooting for help troubleshooting issues with XCopy MSBuild" - throw - } - Unzip $packagePath $packageDir - } - - return Join-Path $packageDir 'tools' -} - # # Locates Visual Studio instance that meets the minimal requirements specified by tools.vs object in global.json. # @@ -592,6 +528,11 @@ function LocateVisualStudio([object]$vsRequirements = $null){ return $null } + if ($null -eq $vsInfo -or $vsInfo.Count -eq 0) { + throw "No instance of Visual Studio meeting the requirements specified was found. Requirements: $($args -join ' ')" + return $null + } + # use first matching instance return $vsInfo[0] } @@ -627,7 +568,7 @@ function InitializeBuildTool() { $buildTool = @{ Path = $dotnetPath; Command = 'msbuild'; Tool = 'dotnet'; Framework = 'net' } } elseif ($msbuildEngine -eq "vs") { try { - $msbuildPath = InitializeVisualStudioMSBuild -install:$restore + $msbuildPath = InitializeVisualStudioMSBuild } catch { Write-PipelineTelemetryError -Category 'InitializeToolset' -Message $_ ExitWithExitCode 1 @@ -836,6 +777,10 @@ function MSBuild-Core() { $cmdArgs += ' /p:TreatWarningsAsErrors=false' } + if ($warnAsError -and $warnNotAsError) { + $cmdArgs += " /warnnotaserror:$warnNotAsError /p:AdditionalWarningsNotAsErrors=$warnNotAsError" + } + foreach ($arg in $args) { if ($null -ne $arg -and $arg.Trim() -ne "") { if ($arg.EndsWith('\')) { diff --git a/eng/common/tools.sh b/eng/common/tools.sh index 1b296f646c2..1e37fd95b21 100755 --- a/eng/common/tools.sh +++ b/eng/common/tools.sh @@ -52,6 +52,9 @@ fi # Configures warning treatment in msbuild. warn_as_error=${warn_as_error:-true} +# Specifies semi-colon delimited list of warning codes that should not be treated as errors. +warn_not_as_error=${warn_not_as_error:-''} + # True to attempt using .NET Core already that meets requirements specified in global.json # installed on the machine instead of downloading one. use_installed_dotnet_cli=${use_installed_dotnet_cli:-true} @@ -115,9 +118,6 @@ function InitializeDotNetCli { local install=$1 - # Don't resolve runtime, shared framework, or SDK from other locations to ensure build determinism - export DOTNET_MULTILEVEL_LOOKUP=0 - # Disable first run since we want to control all package sources export DOTNET_NOLOGO=1 @@ -148,7 +148,11 @@ function InitializeDotNetCli { if [[ $global_json_has_runtimes == false && -n "${DOTNET_INSTALL_DIR:-}" && -d "$DOTNET_INSTALL_DIR/sdk/$dotnet_sdk_version" ]]; then dotnet_root="$DOTNET_INSTALL_DIR" else - dotnet_root="${repo_root}.dotnet" + if [[ -n "${DOTNET_GLOBAL_INSTALL_DIR:-}" ]]; then + dotnet_root="$DOTNET_GLOBAL_INSTALL_DIR" + else + dotnet_root="${repo_root}.dotnet" + fi export DOTNET_INSTALL_DIR="$dotnet_root" @@ -166,7 +170,6 @@ function InitializeDotNetCli { # build steps from using anything other than what we've downloaded. Write-PipelinePrependPath -path "$dotnet_root" - Write-PipelineSetVariable -name "DOTNET_MULTILEVEL_LOOKUP" -value "0" Write-PipelineSetVariable -name "DOTNET_NOLOGO" -value "1" # return value @@ -188,6 +191,8 @@ function InstallDotNet { local version=$2 local runtime=$4 + # For performance this check is duplicated in src/Microsoft.DotNet.Arcade.Sdk/src/InstallDotNetCore.cs + # if you are making changes here, consider if you need to make changes there as well. local dotnetVersionLabel="'$runtime v$version'" if [[ -n "${4:-}" ]] && [ "$4" != 'sdk' ]; then runtimePath="$root" @@ -532,7 +537,12 @@ function MSBuild-Core { mt_switch="-mt" fi - RunBuildTool "$_InitializeBuildToolCommand" /m /nologo /clp:Summary /v:$verbosity /nr:$node_reuse $warnaserror_switch $mt_switch /p:TreatWarningsAsErrors=$warn_as_error /p:ContinuousIntegrationBuild=$ci "$@" + local warnnotaserror_switch="" + if [[ -n "$warn_not_as_error" && "$warn_as_error" == true ]]; then + warnnotaserror_switch="/warnnotaserror:$warn_not_as_error /p:AdditionalWarningsNotAsErrors=$warn_not_as_error" + fi + + RunBuildTool "$_InitializeBuildToolCommand" /m /nologo /clp:Summary /v:$verbosity /nr:$node_reuse $warnaserror_switch $mt_switch $warnnotaserror_switch /p:TreatWarningsAsErrors=$warn_as_error /p:ContinuousIntegrationBuild=$ci "$@" } function GetDarc { diff --git a/global.json b/global.json index bd485523f54..ab623d776cd 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "10.0.105", + "version": "11.0.100-preview.3.26170.106", "allowPrerelease": true, "paths": [ ".dotnet", @@ -12,7 +12,7 @@ "runner": "Microsoft.Testing.Platform" }, "tools": { - "dotnet": "10.0.105", + "dotnet": "11.0.100-preview.3.26170.106", "vs": { "version": "18.0", "components": [ @@ -22,7 +22,7 @@ "xcopy-msbuild": "18.0.0" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "10.0.0-beta.26208.4", + "Microsoft.DotNet.Arcade.Sdk": "11.0.0-beta.26211.1", "Microsoft.DotNet.Helix.Sdk": "8.0.0-beta.23255.2" } }