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
-
-
-
-```
-


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