Repository navigation
Expand file tree
/
Copy pathDirectory.Build.props
More file actions
129 lines (115 loc) · 8.84 KB
/
Copy pathDirectory.Build.props
File metadata and controls
129 lines (115 loc) · 8.84 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
<Project>
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<!-- FS-GG house style: signatures are the contract, builds are reproducible. -->
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Deterministic>true</Deterministic>
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
<!-- No RestoreLockedMode here, deliberately. A condition on $(ContinuousIntegrationBuild) stood
here and was DEAD: nothing in this repo sets that property, and the SDK does not infer it
from GitHub Actions — so it never evaluated true, and it read as a guarantee that locked
restore happens in CI while providing none (review 2026-07-16 §3.12).
Locked restore is asserted where it is actually wanted, explicitly and visibly: the
locked-mode restore flag is passed in gate.yml's cold restore, and in BOTH of release.yml's
jobs. A dead property that looks like a safety net is worse than no property.
(No double hyphen above, deliberately: XML forbids one inside a comment.) -->
</PropertyGroup>
<!-- ONE AUTHORITATIVE FEED, ON EVERY MACHINE (#31).
================================================
`nuget.config` promises a hermetic restore: nuget.org and nothing else. It could not keep that
promise, and its own comment already feared exactly this. The F# SDK adds its BUNDLED copy of
FSharp.Core as an extra restore source through MSBuild rather than through NuGet's source list,
so `<clear />` under `<packageSources>` never sees it:
Microsoft.FSharp.NetSdk.targets
<PropertyGroup Condition=" '$(DisableImplicitLibraryPacksFolder)' != 'true' ">
<RestoreAdditionalProjectSources Condition="Exists('$(_FSharpCoreLibraryPacksFolder)')">
$(RestoreAdditionalProjectSources);$(_FSharpCoreLibraryPacksFolder)
On an unpatched checkout that really does evaluate to `;<sdk>/FSharp/library-packs` — see for
yourself with `dotnet msbuild <proj> -getProperty:RestoreAdditionalProjectSources`.
The bundled copy is a DIFFERENT BUILD of FSharp.Core from the one nuget.org serves, with a
different lock `contentHash`. Whichever source wins writes ITS hash into packages.lock.json, so
the lock records a MACHINE-DEPENDENT value and a locked-mode restore on a machine that resolves
the other way fails NU1403. That is #31 — and it is why `c2a0ead` ("Regenerate lockfiles from a
clean nuget.org restore") already had to be done once: regenerating the lock only re-picks a
winner, it does not stop the coin toss.
`DisableImplicitLibraryPacksFolder` is the SDK's own public switch for this (it has shipped
since at least 6.0.4xx, and `Microsoft.NET.NuGetOfflineCache.targets` honours it too). Prefer
it over overriding the private `_FSharpCoreLibraryPacksFolder`, which is an implementation
detail that could be renamed without warning.
Belt AND braces: `nuget.config` also declares a `packageSourceMapping`, which is the structural
guarantee — NuGet then refuses ANY source not named in the mapping, including sources appended
by MSBuild, so this stays fixed even if the SDK renames the switch above.
Neither change touches WHICH FSharp.Core is used — the version is still pinned by
Directory.Packages.props. Only WHERE it comes from, so that every machine agrees.
Trade-off, accepted: the bundled copy was also a genuine air-gapped fallback, so a restore with
NO reachable source now fails NU1100 instead of quietly resolving FSharp.Core from the SDK. A
reproducible lock file is worth more than an offline restore nobody in this repo performs. -->
<PropertyGroup>
<DisableImplicitLibraryPacksFolder>true</DisableImplicitLibraryPacksFolder>
</PropertyGroup>
<!-- Package metadata for the publishable FS.GG.Audio.* set (org house style; mirrors the
sibling FS-GG repos). Only IsPackable=true projects carry it into a .nupkg. -->
<PropertyGroup Label="Package">
<Authors>FS.GG Contributors</Authors>
<Company>FS.GG Contributors</Company>
<RepositoryType>git</RepositoryType>
<PackageProjectUrl>https://github.com/FS-GG/FS.GG.Audio</PackageProjectUrl>
<RepositoryUrl>https://github.com/FS-GG/FS.GG.Audio.git</RepositoryUrl>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
<PackageTags>fs-gg;audio;fsharp;game-audio</PackageTags>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<PackageReadmeFile Condition="Exists('$(MSBuildProjectDirectory)/README.md')">README.md</PackageReadmeFile>
</PropertyGroup>
<!-- PackageReadmeFile only names the readme; the file still has to be packed. Each publishable
project carries its own README.md next to the .fsproj — that is what nuget.org renders.
Item conditions evaluate after every property, so IsPackable (set in the .fsproj body, below
this import) is readable here — unlike in the PropertyGroup above. -->
<ItemGroup Condition="'$(IsPackable)' == 'true' And Exists('$(MSBuildProjectDirectory)/README.md')">
<None Include="$(MSBuildProjectDirectory)/README.md" Pack="true" PackagePath="\" Visible="false" />
</ItemGroup>
<!-- THE LGPL NOTICE HAS TO TRAVEL WITH THE BINARY (review 2026-07-16 §3.9).
================================================================
`Silk.NET.OpenAL.Soft.Native` is LGPL-2.0-or-later (and requireLicenseAcceptance=true), and it
puts an OpenAL Soft shared library into the output of every project that restores it. Our
packages stamp PackageLicenseExpression=MIT. That combination is fine — the native is a
separate, dynamically-linked, replaceable library, which is the whole point of DEC-001 — but it
is only fine if the recipient is TOLD, and the LGPL's terms for a work that merely uses the
library turn on exactly that telling.
The analysis existed and was sound. It lived in Directory.Packages.props and work/002-audio-host/,
where no consumer will ever read it: the charter reasons explicitly about "the LGPL-clean path
for closed-source games", which is precisely the reader who needed it and precisely the one who
could not get it. This packs THIRD-PARTY-NOTICES.md into the nupkg so the notice arrives with
the bytes it is about.
Opt-IN per project, via $(RedistributesOpenAlNative), rather than a blanket include: the notice
says "this package redistributes OpenAL Soft", and that is FALSE of FS.GG.Audio.Core, which has
no native dependency at all (FR-006). A licence notice that overclaims is its own kind of wrong,
and Core's freedom from the native is a property worth not muddying. Host declares it directly;
Engine and Elmish inherit the native transitively through Host, so they redistribute it just as
surely and say so too. -->
<ItemGroup Label="third-party-notices"
Condition="'$(RedistributesOpenAlNative)' == 'true' And '$(IsPackable)' != 'false'">
<None Include="$(MSBuildThisFileDirectory)THIRD-PARTY-NOTICES.md" Pack="true" PackagePath="\" Visible="false" />
</ItemGroup>
<!-- Pack each project's own .fsi into its nupkg under api-surface/ (cross-repo Audio#106):
FS.GG.Rendering retires its hand-copied scaffold API mirror (#694) by GENERATING it from the
PINNED package a scaffolded product restores. Four mirror files (Audio.Core/Audio.fsi,
Audio.Elmish/Elmish.fsi, Audio.Engine/Engine.fsi, Audio.Host/Host.fsi) have no source until
the FS.GG.Audio.* packages ship their .fsi. Notes that cost time if got wrong:
- PackagePath is the file's FULL target path (%(Filename)%(Extension)), NOT a folder with a
trailing slash — a folder makes NuGet append the recursive path a second time.
- Condition on IsPackable != 'false' (its default-true is applied in .targets, AFTER item
evaluation, so == 'true' silently skips a project that never sets it by hand); guard on
PackageId != '' too — both are set explicitly by every packable project here. (The README
group above can use == 'true' because it only ever needs the four projects that set it.)
api-surface/ is an inert custom folder: carried in the nupkg, never fed to a consumer's
compiler, so a restoring product's build is unchanged. Mirrors FS.GG.Rendering PR #782. -->
<ItemGroup Label="api-surface" Condition="'$(IsPackable)' != 'false' And '$(PackageId)' != ''">
<None Include="$(MSBuildProjectDirectory)/**/*.fsi"
Exclude="$(MSBuildProjectDirectory)/bin/**;$(MSBuildProjectDirectory)/obj/**"
Pack="true"
PackagePath="api-surface/%(RecursiveDir)%(Filename)%(Extension)" />
</ItemGroup>
</Project>