From bee6d8307bc8eaeac91bbda6457b615f4a6892e0 Mon Sep 17 00:00:00 2001 From: Daniel Cazzulino Date: Mon, 1 Sep 2025 18:33:11 -0300 Subject: [PATCH 1/4] Demo can now rely on plain build working Since we support automatic restore of file-app package references in a single run. --- .github/workflows/demos.yml | 9 ++++++++- src/Demo/Demo.csproj | 4 +--- src/Demo/Demo.slnx | 3 --- 3 files changed, 9 insertions(+), 7 deletions(-) delete mode 100644 src/Demo/Demo.slnx diff --git a/.github/workflows/demos.yml b/.github/workflows/demos.yml index 2298ed8..79165d4 100644 --- a/.github/workflows/demos.yml +++ b/.github/workflows/demos.yml @@ -40,6 +40,13 @@ jobs: $version = gci bin | select -first 1 -expandproperty BaseName | %{ $_.Substring(11) } pushd src/Demo jq --arg version "$version" '.["msbuild-sdks"].SmallSharp = $version' global.json > temp.json && mv temp.json global.json + # build with each top-level file as the active one - gci *.cs | %{ dotnet build -p:ActiveFile=$_.name } + foreach ($file in gci *.cs) { + dotnet build -p:ActiveFile=$($file.Name) + if ($LASTEXITCODE -ne 0) { + Write-Error "Build failed for $($file.Name)" + exit $LASTEXITCODE + } + } \ No newline at end of file diff --git a/src/Demo/Demo.csproj b/src/Demo/Demo.csproj index 759ae2b..e766b81 100644 --- a/src/Demo/Demo.csproj +++ b/src/Demo/Demo.csproj @@ -1,6 +1,4 @@ - - - + Exe diff --git a/src/Demo/Demo.slnx b/src/Demo/Demo.slnx deleted file mode 100644 index 90daed6..0000000 --- a/src/Demo/Demo.slnx +++ /dev/null @@ -1,3 +0,0 @@ - - - From a5bc9c274fddbf32850a812a1ed239b3da1ef605 Mon Sep 17 00:00:00 2001 From: Daniel Cazzulino Date: Mon, 1 Sep 2025 18:45:02 -0300 Subject: [PATCH 2/4] Fix typo in SDK reference --- src/Demo/nuget.config | 2 +- src/SmallSharp/Sdk.props | 2 +- src/SmallSharp/Sdk.targets | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Demo/nuget.config b/src/Demo/nuget.config index 5b6b781..6b95309 100644 --- a/src/Demo/nuget.config +++ b/src/Demo/nuget.config @@ -2,7 +2,7 @@ - + diff --git a/src/SmallSharp/Sdk.props b/src/SmallSharp/Sdk.props index 69cd5fb..987fe39 100644 --- a/src/SmallSharp/Sdk.props +++ b/src/SmallSharp/Sdk.props @@ -14,7 +14,7 @@ - - Date: Wed, 3 Sep 2025 16:56:59 -0300 Subject: [PATCH 3/4] Improve automatic restore support in SDK mode Also provide better diagnostics when package is used instead of SDK mode. --- .netconfig | 4 +-- readme.md | 32 +++++++++---------- src/Demo/.gitignore | 1 + src/Demo/global.json | 4 +-- src/SmallSharp/EmitTargets.cs | 39 ++++++++---------------- src/SmallSharp/Sdk.targets | 4 +-- src/SmallSharp/SmallSharp.Version.props | 5 +++ src/SmallSharp/SmallSharp.csproj | 9 ++++++ src/SmallSharp/SmallSharp.props | 2 ++ src/SmallSharp/SmallSharp.targets | 32 ++++--------------- src/SmallSharp/TaskItemFactory.cs | 13 ++++++++ src/demo.ps1 | 18 +++++++++++ src/kzu.snk | Bin 596 -> 0 bytes 13 files changed, 85 insertions(+), 78 deletions(-) create mode 100644 src/Demo/.gitignore create mode 100644 src/SmallSharp/SmallSharp.Version.props create mode 100644 src/SmallSharp/TaskItemFactory.cs create mode 100644 src/demo.ps1 delete mode 100644 src/kzu.snk diff --git a/.netconfig b/.netconfig index 9266083..041439b 100644 --- a/.netconfig +++ b/.netconfig @@ -91,9 +91,7 @@ sha = 4339749ef4b8f66def75931df09ef99c149f8421 [file "src/kzu.snk"] url = https://github.com/devlooped/oss/blob/main/src/kzu.snk - etag = b8d789b5b6bea017cdcc8badcea888ad78de3e34298efca922054e9fb0e7b6b9 - weak - sha = 0683ee777d7d878d4bf013d7deea352685135a05 + skip [file ".github/workflows/pages.yml"] url = https://github.com/clarius/pages/blob/main/.github/workflows/pages.yml etag = c52b3f0463b88abf696ddf2c6902675e0bc9d1e812bb317cb758221d75330b56 diff --git a/readme.md b/readme.md index 0fe2d2f..a5d5219 100644 --- a/readme.md +++ b/readme.md @@ -62,7 +62,7 @@ modify any of the others since they automatically become *None* items. > [!TIP] > An initial build after selection change migh be needed to restore the packages and compile the -> selected file +> selected file, unless you're using the SDK mode for SmallSharp (see below). All compile files directly under the project directory root are considered top-level programs for selection and compilation purposes. If you need to share code among them, you can place additional @@ -93,10 +93,16 @@ and adding a couple extra properties to the project file: ``` -If your file-based apps use the `#:sdk` [directive](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/preprocessor-directives#file-based-apps), -you need to add SmallSharp as an SDK reference instead so the SDK is picked up by the -generated targets/props instead of the project file. You also don't need the additional -properties since the SDK mode sets them automatically for you: +There are some limitations with this mode, however: +* You cannot use the `#:sdk` [directive](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/preprocessor-directives#file-based-apps) + to specify a different SDK per file, since the project file already specifies one. +* CLI-based builds may require multiple passes to restore and build the selected file, since + the package is only restored after the first build. +* You must add ImportProjectExtensionProps/ImportProjectExtensionTargets manually, polluting the + project file. + +So the recommended way to use SmallSharp is via the SDK mode, which results in a more streamlined +and seamless experience across IDE and CLI builds: ```xml @@ -109,6 +115,9 @@ properties since the SDK mode sets them automatically for you: ``` +The SDK mode will always produce a successful build in a single `dotnet build` pass even if you +change the `ActiveFile` between builds. + > [!IMPORTANT] > If no `#:sdk` directive is provided by a specific C# file-based app, the `Microsoft.NET.SDK` will be > used by default in this SDK mode. @@ -157,19 +166,6 @@ since the "Main" file selection is performed exclusively via MSBuild item manipu > SDK reference, and do all project/package references in the top-level files using the `#:package` and > `#:property` directives for improved isolation between the different file-based apps. -```xml - - - - - - Exe - net10.0 - - - -``` - ![run humanizer file](https://raw.githubusercontent.com/devlooped/SmallSharp/main/assets/img/runfile1.png) ![run mcp file](https://raw.githubusercontent.com/devlooped/SmallSharp/main/assets/img/runfile2.png) diff --git a/src/Demo/.gitignore b/src/Demo/.gitignore new file mode 100644 index 0000000..d66183a --- /dev/null +++ b/src/Demo/.gitignore @@ -0,0 +1 @@ +Demo.sln \ No newline at end of file diff --git a/src/Demo/global.json b/src/Demo/global.json index 9b93be7..8f08a24 100644 --- a/src/Demo/global.json +++ b/src/Demo/global.json @@ -1,5 +1,5 @@ { "msbuild-sdks": { - "SmallSharp": "2.0.0" + "SmallSharp": "42.537.1166" } -} \ No newline at end of file +} diff --git a/src/SmallSharp/EmitTargets.cs b/src/SmallSharp/EmitTargets.cs index db322b1..e2aa421 100644 --- a/src/SmallSharp/EmitTargets.cs +++ b/src/SmallSharp/EmitTargets.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.RegularExpressions; @@ -6,6 +7,7 @@ using System.Xml.Linq; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; +using static SmallSharp.TaskItemFactory; namespace SmallSharp; @@ -27,9 +29,6 @@ public class EmitTargets : Task [Required] public required string TargetsFile { get; set; } - [Required] - public bool UsingSmallSharpSDK { get; set; } = false; - [Output] public ITaskItem[] Packages { get; set; } = []; @@ -39,9 +38,6 @@ public class EmitTargets : Task [Output] public ITaskItem[] Properties { get; set; } = []; - [Output] - public bool Success { get; set; } = false; - public override bool Execute() { if (StartupFile is null) @@ -65,10 +61,7 @@ public override bool Execute() var id = match.Groups[1].Value.Trim(); var version = match.Groups[2].Value.Trim(); - packages.Add(new TaskItem(id, new Dictionary - { - { "Version", version } - })); + packages.Add(NewTaskItem(id, [("Version", version)])); items.Add(new XElement("PackageReference", new XAttribute("Include", id), @@ -80,10 +73,7 @@ public override bool Execute() var version = sdkMatch.Groups[2].Value.Trim(); if (!string.IsNullOrEmpty(version)) { - sdkItems.Add(new TaskItem(name, new Dictionary - { - { "Version", version } - })); + sdkItems.Add(NewTaskItem(name, [("Version", version)])); sdks.Add([new XAttribute("Sdk", name), new XAttribute("Version", version)]); } else @@ -97,10 +87,7 @@ public override bool Execute() var name = propMatch.Groups[1].Value.Trim(); var value = propMatch.Groups[2].Value.Trim(); - propItems.Add(new TaskItem(name, new Dictionary - { - { "Value", value } - })); + propItems.Add(NewTaskItem(name, [("Value", value)])); properties.Add(new XElement(name, value)); } } @@ -109,12 +96,6 @@ public override bool Execute() Sdks = [.. sdkItems]; Properties = [.. propItems]; - if (sdks.Count > 0 && !UsingSmallSharpSDK) - { - Log.LogError($"When using #:sdk directive(s), you must use SmallSharp as an SDK: ."); - return false; - } - // We only emit the default SDK if the SmallSharpSDK is in use, since otherwise the // project file is expected to define its own SDK and we'd be duplicating it. if (sdks.Count == 0) @@ -131,14 +112,18 @@ public override bool Execute() WriteXml(Path.Combine(BaseIntermediateOutputPath, "SmallSharp.sdk.targets"), new XElement("Project", sdks.Select(x => new XElement("Import", [new XAttribute("Project", "Sdk.targets"), .. x])))); - WriteXml(PropsFile, new XElement("Project")); + WriteXml(PropsFile, new XElement("Project", + new XElement("PropertyGroup", + [new XElement("SmallSharpProjectExtensionPropsImported", "true")]))); - Success = true; return true; } void WriteXml(string path, XElement root) { + if (Path.GetDirectoryName(path) is { } dir) + Directory.CreateDirectory(dir); + using var writer = XmlWriter.Create(path, new XmlWriterSettings { Indent = true }); root.Save(writer); } diff --git a/src/SmallSharp/Sdk.targets b/src/SmallSharp/Sdk.targets index 1ba589d..5e5046e 100644 --- a/src/SmallSharp/Sdk.targets +++ b/src/SmallSharp/Sdk.targets @@ -8,9 +8,9 @@ - + Condition="'$(StartupFile)' != '' and Exists('$(StartupFile)') and '$(SmallSharpProjectExtensionPropsImported)' != 'true'"> diff --git a/src/SmallSharp/SmallSharp.Version.props b/src/SmallSharp/SmallSharp.Version.props new file mode 100644 index 0000000..237ff18 --- /dev/null +++ b/src/SmallSharp/SmallSharp.Version.props @@ -0,0 +1,5 @@ + + + 42.42.42 + + \ No newline at end of file diff --git a/src/SmallSharp/SmallSharp.csproj b/src/SmallSharp/SmallSharp.csproj index f530c0d..8f2e97c 100644 --- a/src/SmallSharp/SmallSharp.csproj +++ b/src/SmallSharp/SmallSharp.csproj @@ -34,6 +34,8 @@ + + @@ -46,4 +48,11 @@ + + + + + diff --git a/src/SmallSharp/SmallSharp.props b/src/SmallSharp/SmallSharp.props index e9d4037..2fad040 100644 --- a/src/SmallSharp/SmallSharp.props +++ b/src/SmallSharp/SmallSharp.props @@ -10,4 +10,6 @@ false + + \ No newline at end of file diff --git a/src/SmallSharp/SmallSharp.targets b/src/SmallSharp/SmallSharp.targets index f019d64..f2ac085 100644 --- a/src/SmallSharp/SmallSharp.targets +++ b/src/SmallSharp/SmallSharp.targets @@ -37,10 +37,12 @@ - - + @@ -169,35 +171,13 @@ - - - - - - - $(MSBuildProjectExtensionsPath)smallsharp.assets.json - - - - - - - - - $(DynamicProjectAssetsFile) - - + diff --git a/src/SmallSharp/TaskItemFactory.cs b/src/SmallSharp/TaskItemFactory.cs new file mode 100644 index 0000000..6f48827 --- /dev/null +++ b/src/SmallSharp/TaskItemFactory.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using System.Linq; +using Microsoft.Build.Utilities; + +namespace SmallSharp; + +static class TaskItemFactory +{ + public static TaskItem NewTaskItem(string itemSpec, Dictionary metadata) => new(itemSpec, metadata); + + public static TaskItem NewTaskItem(string itemSpec, params (string Key, string Value)[] metadata) + => new(itemSpec, metadata.ToDictionary(x => x.Key, x => x.Value)); +} diff --git a/src/demo.ps1 b/src/demo.ps1 new file mode 100644 index 0000000..8963212 --- /dev/null +++ b/src/demo.ps1 @@ -0,0 +1,18 @@ +pushd $PSScriptRoot/.. +dotnet build -p:PackOnBuild=true +$version = gci bin | select -first 1 -expandproperty BaseName | %{ $_.Substring(11) } +pushd src/Demo +jq --arg version "$version" '.["msbuild-sdks"].SmallSharp = $version' global.json > temp.json && del global.json && mv temp.json global.json + +# build with each top-level file as the active one +foreach ($file in gci *.cs) { + rm -r -fo obj -ea 0 + dotnet build Demo.csproj -p:ActiveFile=$($file.Name) -bl + if ($LASTEXITCODE -ne 0) { + Write-Error "Build failed for $($file.Name)" + popd; popd; + exit $LASTEXITCODE + } +} + +popd; popd; diff --git a/src/kzu.snk b/src/kzu.snk deleted file mode 100644 index 8e181aea272d85cb1f733b03ec744dc0b909aa76..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 596 zcmV-a0;~N80ssI2Bme+XQ$aES1ONa50096|6<^TqC=2I}fy|ebBk>ZR#oZ|F&Yam# z)_I8rIm*>c5+1#_2KZ%`SXhny3*T6 z*Hl98Gn760J$8)`H|s1CT;KcAJa&xIsA}XdJYoZDmrV!J#e! z{hX@1GFmPRLr@90Ht6Re*WT@R(h$oT(}Ndt0-9{N@~o!!Kb>)5mOJPZska|U$ddjN zmH)97_!+N{F_6dwE>D71OT9<$emvS2N+4V`-`=v6F+>zNj6c3mYfJvYYPSqjJQ(%* zqN8iz#aR{3iaTMs5!q+1Tcq+^XKsaK)tWrcS#R3ekawH3ZLi90fvZz`qr1uiS&H0- zV_Im+1%b)21TWLSobv+^6V|+Ktl;=fyLWocD?XAMmupcMg&VnbU-6{TIP}A$^sek8 zA`ia`b`%%>JEKjyqBDOAIxx|mg*}iZg0s5WxV`eu{0<1HRCWP|hZ2e41)OY;lo7rE isJbUAkndKa&zHpXW)3~Yn&^IbW*8 Date: Thu, 4 Sep 2025 12:34:32 -0300 Subject: [PATCH 4/4] Improve integration with CLI restore in SDK mode In SDK mode we can just always read the package references from the startup file and therefore Restore will Just Work. In non-SDK mode we'll depend on an initial run and it's mostly for IDE usage where this problem doesn't exist because VS will detect the generated targets changed and will automatically invoke a restore while you're editing. --- src/SmallSharp/EmitTargets.cs | 28 +++++++++++++++++++++++++++- src/SmallSharp/Sdk.targets | 5 +++-- src/SmallSharp/SmallSharp.targets | 5 ++++- src/demo.ps1 | 4 ++-- 4 files changed, 36 insertions(+), 6 deletions(-) diff --git a/src/SmallSharp/EmitTargets.cs b/src/SmallSharp/EmitTargets.cs index e2aa421..344aa43 100644 --- a/src/SmallSharp/EmitTargets.cs +++ b/src/SmallSharp/EmitTargets.cs @@ -29,6 +29,11 @@ public class EmitTargets : Task [Required] public required string TargetsFile { get; set; } + [Required] + public required bool UsingSDK { get; set; } + + public ITaskItem[] PackageReferences { get; set; } = []; + [Output] public ITaskItem[] Packages { get; set; } = []; @@ -38,6 +43,9 @@ public class EmitTargets : Task [Output] public ITaskItem[] Properties { get; set; } = []; + [Output] + public bool RestoreNeeded { get; set; } = false; + public override bool Execute() { if (StartupFile is null) @@ -103,7 +111,8 @@ public override bool Execute() WriteXml(TargetsFile, new XElement("Project", new XElement("PropertyGroup", properties), - new XElement("ItemGroup", items) + // don't emit package references in SDK mode, since we'll add them from the SDK targets. + UsingSDK ? new XElement("ItemGroup") : new XElement("ItemGroup", items) )); WriteXml(Path.Combine(BaseIntermediateOutputPath, "SmallSharp.sdk.props"), new XElement("Project", @@ -116,6 +125,23 @@ public override bool Execute() new XElement("PropertyGroup", [new XElement("SmallSharpProjectExtensionPropsImported", "true")]))); + // Determine if a restore is needed: if any discovered #:package (id+version) is not already + // present in the incoming PackageReferences list. + foreach (var pkg in packages) + { + var id = pkg.ItemSpec; + var version = pkg.GetMetadata("Version"); + var exists = PackageReferences?.Any(r => + string.Equals(r.ItemSpec, id, StringComparison.OrdinalIgnoreCase) && + string.Equals(r.GetMetadata("Version"), version, StringComparison.OrdinalIgnoreCase)) == true; + + if (!exists) + { + RestoreNeeded = true; + break; + } + } + return true; } diff --git a/src/SmallSharp/Sdk.targets b/src/SmallSharp/Sdk.targets index 5e5046e..bd4ce7d 100644 --- a/src/SmallSharp/Sdk.targets +++ b/src/SmallSharp/Sdk.targets @@ -8,9 +8,10 @@ - + Condition="'$(StartupFile)' != '' and Exists('$(StartupFile)')"> diff --git a/src/SmallSharp/SmallSharp.targets b/src/SmallSharp/SmallSharp.targets index f2ac085..a5e5b11 100644 --- a/src/SmallSharp/SmallSharp.targets +++ b/src/SmallSharp/SmallSharp.targets @@ -168,13 +168,16 @@ - + diff --git a/src/demo.ps1 b/src/demo.ps1 index 8963212..3063ce5 100644 --- a/src/demo.ps1 +++ b/src/demo.ps1 @@ -6,8 +6,8 @@ jq --arg version "$version" '.["msbuild-sdks"].SmallSharp = $version' global.jso # build with each top-level file as the active one foreach ($file in gci *.cs) { - rm -r -fo obj -ea 0 - dotnet build Demo.csproj -p:ActiveFile=$($file.Name) -bl + # rm -r -fo obj -ea 0 + dotnet build Demo.csproj -p:ActiveFile=$($file.Name) -bl:"$($file.BaseName).binlog" if ($LASTEXITCODE -ne 0) { Write-Error "Build failed for $($file.Name)" popd; popd;