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/.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/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 @@ - - - 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/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/EmitTargets.cs b/src/SmallSharp/EmitTargets.cs index db322b1..344aa43 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; @@ -28,7 +30,9 @@ public class EmitTargets : Task public required string TargetsFile { get; set; } [Required] - public bool UsingSmallSharpSDK { get; set; } = false; + public required bool UsingSDK { get; set; } + + public ITaskItem[] PackageReferences { get; set; } = []; [Output] public ITaskItem[] Packages { get; set; } = []; @@ -40,7 +44,7 @@ public class EmitTargets : Task public ITaskItem[] Properties { get; set; } = []; [Output] - public bool Success { get; set; } = false; + public bool RestoreNeeded { get; set; } = false; public override bool Execute() { @@ -65,10 +69,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 +81,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 +95,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 +104,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) @@ -122,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", @@ -131,14 +121,35 @@ 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")]))); + + // 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; + } + } - 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.props b/src/SmallSharp/Sdk.props index 69cd5fb..987fe39 100644 --- a/src/SmallSharp/Sdk.props +++ b/src/SmallSharp/Sdk.props @@ -14,7 +14,7 @@ - - - + Condition="'$(StartupFile)' != '' and Exists('$(StartupFile)')"> 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..a5e5b11 100644 --- a/src/SmallSharp/SmallSharp.targets +++ b/src/SmallSharp/SmallSharp.targets @@ -37,10 +37,12 @@ - - + @@ -166,38 +168,19 @@ - - + - - - - - - $(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..3063ce5 --- /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:"$($file.BaseName).binlog" + 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 8e181ae..0000000 Binary files a/src/kzu.snk and /dev/null differ