Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
* Add `Argu.Samples.Introspect` sample [#298](https://github.com/fsprojects/Argu/pull/298) [@dimension-zero](https://github.com/dimension-zero)
* Add `ArgumentParser.Parse(ParseConfig)` [#307](https://github.com/fsprojects/Argu/pull/307) [@dimension-zero](https://github.com/dimension-zero)
* Add `ArgumentParser.PrintUsage(..., ?UsageStrings)` for localization support [#303](https://github.com/fsprojects/Argu/pull/303) [@dimension-zero](https://github.com/dimension-zero)
* Add AOT annotations on [#314](https://github.com/fsprojects/Argu/pull/314) [@dimension-zero](https://github.com/dimension-zero)
* Obsolete `EqualsAssignmentAttribute`, `ColonAssignmentAttribute`, `CustomAssignmentAttribute`, `EqualsAssignmentOrSpacedAttribute`, `ColonAssignmentOrSpacedAttribute` and `CustomAssignmentOrSpacedAttribute` [#315](https://github.com/fsprojects/Argu/pull/315) [@dimension-zero](https://github.com/dimension-zero)
* Obsolete `PostProcessResult`, `PostProcessResults`, `TryPostProcessResult` [#296](https://github.com/fsprojects/Argu/pull/296) [@dimension-zero](https://github.com/dimension-zero)

Expand Down
7 changes: 7 additions & 0 deletions src/Argu/Argu.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,15 @@
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
<PackageIcon>logo.png</PackageIcon>
<Nullable>enable</Nullable>
<!-- IsTrimmable / IsAotCompatible are conditioned on net8.0+ since the
SDK rejects them on netstandard2.0 targets. The IL3050 / IL2026
opt-outs are still emitted via [<RequiresDynamicCode>] /
[<RequiresUnreferencedCode>] attributes carried by the assembly. -->
<IsTrimmable Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net8.0'))">true</IsTrimmable>
<IsAotCompatible Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net8.0'))">false</IsAotCompatible>
</PropertyGroup>
<ItemGroup>
<Compile Include="TrimAnnotations.fs"/>
<Compile Include="Types.fs"/>
<Compile Include="Attributes.fs"/>
<Compile Include="Utils.fs"/>
Expand Down
20 changes: 20 additions & 0 deletions src/Argu/ArgumentParser.fs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
namespace Argu

open FSharp.Quotations
open System.Diagnostics.CodeAnalysis

open Argu.UnionArgInfo

/// Configuration record for <see cref="ArgumentParser`1.Parse(ParseConfig)"/>.
Expand Down Expand Up @@ -32,6 +34,14 @@ type ParseConfig =
IgnoreUnrecognized = false
RaiseOnUsage = true }

module internal TrimMessages =
[<Literal>]
let aot =
"Argu reflects over the supplied F# discriminated union via MakeGenericType / Activator.CreateInstance to build its schema. This is not safe under publish-AOT; consider a hand-written parser for AOT scenarios."
[<Literal>]
let trim =
"Argu walks the union schema via System.Reflection (FSharpType.GetUnionCases and friends). The trimmer cannot keep referenced union cases unless the consumer's root sees them; pin the template type with DynamicDependency or DynamicallyAccessedMembers attributes when trimming."

/// The Argu type generates an argument parser given a type argument
/// that is an F# discriminated union. It can then be used to parse command line arguments
/// or XML configuration.
Expand Down Expand Up @@ -135,6 +145,8 @@ and [<Sealed; NoEquality; NoComparison; AutoSerializable(false)>]
/// <param name="usageStringCharacterWidth">Text width used when formatting the usage string. Defaults to 80 chars.</param>
/// <param name="errorHandler">The implementation of IExiter used for error handling. Exception is default.</param>
/// <param name="checkStructure">Indicate if the structure of the arguments discriminated union should be checked for errors.</param>
[<RequiresDynamicCode(TrimMessages.aot)>]
[<RequiresUnreferencedCode(TrimMessages.trim)>]
new (?programName : string, ?helpTextMessage : string, ?usageStringCharacterWidth : int, ?errorHandler : IExiter, ?checkStructure: bool) =
let usageStringCharacterWidth = usageStringCharacterWidth |> Option.defaultWith getDefaultCharacterWidthSafeMin80
let programName = programName |> Option.defaultWith currentProgramName
Expand All @@ -150,6 +162,8 @@ and [<Sealed; NoEquality; NoComparison; AutoSerializable(false)>]
member val ProgramName = _programName

/// <summary>Force a check of the discriminated union structure.</summary>
[<RequiresDynamicCode(TrimMessages.aot)>]
[<RequiresUnreferencedCode(TrimMessages.trim)>]
static member CheckStructure() =
argInfoWithCheck.Value |> ignore

Expand Down Expand Up @@ -313,6 +327,8 @@ type ArgumentParser with
/// <param name="usageStringCharacterWidth">Text width used when formatting the usage string. Defaults to 80 chars.</param>
/// <param name="errorHandler">The implementation of IExiter used for error handling. Exception is default.</param>
/// <param name="checkStructure">Indicate if the structure of the arguments discriminated union should be checked for errors.</param>
[<RequiresDynamicCode(TrimMessages.aot)>]
[<RequiresUnreferencedCode(TrimMessages.trim)>]
Comment thread
bartelink marked this conversation as resolved.
static member Create<'Template when 'Template :> IArgParserTemplate>(?programName : string, ?helpTextMessage : string, ?usageStringCharacterWidth : int, ?errorHandler : IExiter, ?checkStructure: bool) =
new ArgumentParser<'Template>(?programName = programName, ?helpTextMessage = helpTextMessage, ?errorHandler = errorHandler, ?usageStringCharacterWidth = usageStringCharacterWidth, ?checkStructure = checkStructure)

Expand All @@ -327,9 +343,13 @@ module ArgumentParserUtils =
r.CharacterWidth, r.ErrorHandler)

/// converts a sequence of inputs to a ParseResults instance
[<RequiresDynamicCode(TrimMessages.aot)>]
[<RequiresUnreferencedCode(TrimMessages.trim)>]
let toParseResults (inputs : seq<'Template>) : ParseResults<'Template> =
ArgumentParser.Create<'Template>().ToParseResults(inputs)

/// gets the F# union tag representation of given argument instance
[<RequiresDynamicCode(TrimMessages.aot)>]
[<RequiresUnreferencedCode(TrimMessages.trim)>]
let tagOf (input : 'Template) : int =
ArgumentParser.Create<'Template>().GetTag input
40 changes: 40 additions & 0 deletions src/Argu/TrimAnnotations.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Polyfills for AOT/trim diagnostic attributes so that
// netstandard2.0 builds can carry the same metadata as .NET 7+ targets.
// The .NET trimmer / publish-AOT pipeline matches by full type name,
// not by assembly, so internal copies are picked up.
namespace System.Diagnostics.CodeAnalysis

open System

#if !NET7_0_OR_GREATER

/// <summary>
/// Marker indicating that the annotated method requires dynamic-code
/// generation at runtime (e.g. <c>MakeGenericType</c> +
/// <c>Activator.CreateInstance</c>) and is therefore not safe under
/// publish-AOT. Callers that opt into AOT will see an <c>IL3050</c>
/// warning when invoking the annotated member.
/// </summary>
[<AttributeUsage(AttributeTargets.Method ||| AttributeTargets.Constructor ||| AttributeTargets.Class, AllowMultiple = false, Inherited = false)>]
type internal RequiresDynamicCodeAttribute(message : string) =
inherit Attribute()
/// Reason the method requires dynamic code.
member _.Message = message
/// Optional URL pointing at extended documentation.
member val Url : string = null with get, set

/// <summary>
/// Marker indicating that the annotated method uses reflection in a
/// way the trimmer cannot statically analyse (e.g. arbitrary type
/// reads via <c>FSharpType.GetUnionCases</c>). Callers that enable
/// trimming will see an <c>IL2026</c> warning.
/// </summary>
[<AttributeUsage(AttributeTargets.Method ||| AttributeTargets.Constructor ||| AttributeTargets.Class, AllowMultiple = false, Inherited = false)>]
type internal RequiresUnreferencedCodeAttribute(message : string) =
inherit Attribute()
/// Reason the method holds unreferenced-code requirements.
member _.Message = message
/// Optional URL pointing at extended documentation.
member val Url : string = null with get, set

#endif
Loading