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
9 changes: 8 additions & 1 deletion .github/workflows/demos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}

4 changes: 1 addition & 3 deletions .netconfig
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
32 changes: 14 additions & 18 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -93,10 +93,16 @@ and adding a couple extra properties to the project file:
</Project>
```

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
<Project Sdk="SmallSharp/2.1.0">
Expand All @@ -109,6 +115,9 @@ properties since the SDK mode sets them automatically for you:
</Project>
```

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.
Expand Down Expand Up @@ -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
<Project Sdk="Microsoft.NET.Sdk">

<Sdk Name="SmallSharp" Version="2.0.0" />

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
</PropertyGroup>

</Project>
```

![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)
Expand Down
1 change: 1 addition & 0 deletions src/Demo/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Demo.sln
4 changes: 1 addition & 3 deletions src/Demo/Demo.csproj
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">

<Sdk Name="SmallSharp" />
<Project Sdk="SmallSharp">

<PropertyGroup>
<OutputType>Exe</OutputType>
Expand Down
3 changes: 0 additions & 3 deletions src/Demo/Demo.slnx

This file was deleted.

4 changes: 2 additions & 2 deletions src/Demo/global.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"msbuild-sdks": {
"SmallSharp": "2.0.0"
"SmallSharp": "42.537.1166"
}
}
}
2 changes: 1 addition & 1 deletion src/Demo/nuget.config
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<configuration>
<packageSources>
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
<add key="kzu" value="https://api.kzu.app/index.json" />
<add key="kzu" value="https://pkg.kzu.app/index.json" />
<add key="local" value="../../bin/" />
</packageSources>
<activePackageSource>
Expand Down
59 changes: 35 additions & 24 deletions src/SmallSharp/EmitTargets.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Xml;
using System.Xml.Linq;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using static SmallSharp.TaskItemFactory;

namespace SmallSharp;

Expand All @@ -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; } = [];
Expand All @@ -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()
{
Expand All @@ -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<string, string>
{
{ "Version", version }
}));
packages.Add(NewTaskItem(id, [("Version", version)]));

items.Add(new XElement("PackageReference",
new XAttribute("Include", id),
Expand All @@ -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<string, string>
{
{ "Version", version }
}));
sdkItems.Add(NewTaskItem(name, [("Version", version)]));
sdks.Add([new XAttribute("Sdk", name), new XAttribute("Version", version)]);
}
else
Expand All @@ -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<string, string>
{
{ "Value", value }
}));
propItems.Add(NewTaskItem(name, [("Value", value)]));
properties.Add(new XElement(name, value));
}
}
Expand All @@ -109,20 +104,15 @@ 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: <Project Sdk=\"SmallSharp/{ThisAssembly.Project.Version}\">.");
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)
sdks.Add([new XAttribute("Sdk", "Microsoft.NET.Sdk")]);

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",
Expand All @@ -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);
}
Expand Down
2 changes: 1 addition & 1 deletion src/SmallSharp/Sdk.props
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<!-- Import Common.props explicitly we're too early here to use MSBuildProjectExtensionsPath -->
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="'$(MicrosoftCommonPropsHasBeenImported)' != 'true' and Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />

<Import Project="Sdk.props" Sdk="Microsoft.NET.SDK"
<Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk"
Condition="!Exists('$(MSBuildProjectExtensionsPath)SmallSharp.sdks.props')" />

<Import Project="$(MSBuildProjectExtensionsPath)SmallSharp.sdks.props"
Expand Down
7 changes: 4 additions & 3 deletions src/SmallSharp/Sdk.targets
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
<Project>

<Import Project="Sdk.targets" Sdk="Microsoft.NET.SDK"
<Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk"
Condition="!Exists('$(MSBuildProjectExtensionsPath)SmallSharp.sdks.targets')" />

<Import Project="$(MSBuildProjectExtensionsPath)SmallSharp.sdks.targets"
Condition="Exists('$(MSBuildProjectExtensionsPath)SmallSharp.sdks.targets')" />

<Import Project="..\build\SmallSharp.targets" />

<Target Name="ImplicitPackageReferenceFromStartupFile" BeforeTargets="_GenerateProjectRestoreGraphPerFramework"
<Target Name="ImplicitPackageReferenceFromStartupFile"
BeforeTargets="_GenerateProjectRestoreGraphPerFramework;ResolvePackageAssets;CompileDesignTime;CollectUpToDateCheckInputDesignTime"
DependsOnTargets="StartupFile"
Condition="'$(StartupFile)' != '' and Exists('$(StartupFile)') and '$(RestoreNeeded)' == 'true'" >
Condition="'$(StartupFile)' != '' and Exists('$(StartupFile)')">

<!-- Optimize for restore success on first run without previously running our targets -->
<ReadLinesFromFile File="$(StartupFile)">
Expand Down
5 changes: 5 additions & 0 deletions src/SmallSharp/SmallSharp.Version.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<Project>
<PropertyGroup>
<SmallSharpVersion>42.42.42</SmallSharpVersion>
</PropertyGroup>
</Project>
9 changes: 9 additions & 0 deletions src/SmallSharp/SmallSharp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@

<ItemGroup>
<None Include="..\_._" PackFolder="lib\netstandard2.0" Visible="false" />
<None Update="SmallSharp.props" PackFolder="$(PackFolder)" CopyToOutputDirectory="PreserveNewest" />
<None Update="SmallSharp.Version.props" PackFolder="$(PackFolder)" CopyToOutputDirectory="PreserveNewest" />
<None Update="SmallSharp.targets" PackFolder="$(PackFolder)" CopyToOutputDirectory="PreserveNewest" />
<None Update="SmallSharp.Before.targets" PackFolder="$(PackFolder)" CopyToOutputDirectory="PreserveNewest" />
<None Update="Sdk.*" PackFolder="Sdk" CopyToOutputDirectory="PreserveNewest" />
Expand All @@ -46,4 +48,11 @@
<ProjectProperty Include="PackageVersion" />
</ItemGroup>

<Target Name="UpdatePackagingVersion" BeforeTargets="Pack">
<!-- Update packaging version targets -->
<XmlPoke XmlInputPath="$(OutputPath)SmallSharp.Version.props"
Query="/Project/PropertyGroup/SmallSharpVersion"
Value="$(Version)"/>
</Target>

</Project>
2 changes: 2 additions & 0 deletions src/SmallSharp/SmallSharp.props
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@
<UsingSmallSharpSDK Condition="'$(UsingSmallSharpSDK)' == ''">false</UsingSmallSharpSDK>
</PropertyGroup>

<Import Project="SmallSharp.Version.props"/>

</Project>
37 changes: 10 additions & 27 deletions src/SmallSharp/SmallSharp.targets
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,12 @@
<Target Name="StartupFile" BeforeTargets="ResolvePackageAssets;CompileDesignTime;CollectUpToDateCheckInputDesignTime" DependsOnTargets="$(StartupFileDependsOn)" />

<Target Name="EnsureProperties" Condition="'$(CheckSmallSharpRequirements)' != 'false'">
<Error Code="SCS01" Condition="'$(_ImportProjectExtensionProps)' != 'true' or '$(_ImportProjectExtensionTargets)' != 'true'"
<Error Code="SCS02" Condition="'$(_ImportProjectExtensionProps)' != 'true' or '$(_ImportProjectExtensionTargets)' != 'true'"
Text="Setting ImportProjectExtensionProps and ImportProjectExtensionTargets project properties to 'true' is required by SmallSharp to support C# package and project directives." />
<Error Code="SCS02" Condition="'$(ManagePackageVersionsCentrally)' == 'true'"
<Error Code="SCS03" Condition="'$(ManagePackageVersionsCentrally)' == 'true'"
Text="Setting ManagePackageVersionsCentrally to 'true' is not supported by SmallSharp since C# program files can declare package references via #:package directives." />
<Warning Code="SCS04" Condition="'$(UsingSmallSharpSDK)' != 'true'"
Text='For maximum compatibility with file-based apps, use SmallSharp as an SDK instead of a package reference: &lt;Project Sdk="SmallSharp/$(SmallSharpVersion)"&gt;' />
</Target>

<Target Name="CollectStartupFile">
Expand Down Expand Up @@ -166,38 +168,19 @@
<Target Name="EmitTargets" DependsOnTargets="CollectStartupFile;SelectTopLevelCompile;SelectStartupFile"
Inputs="@(Compile);$(ActiveDebugProfile);$(ActiveFile);Properties\launchSettings.json"
Outputs="$(SmallSharpPackagesProps);$(SmallSharpPackagesTargets)">
<EmitTargets StartupFile="$(StartupFile)"
<EmitTargets StartupFile="$(StartupFile)"
UsingSDK="$(UsingSmallSharpSDK)"
PackageReferences="@(PackageReferences)"
PropsFile="$(SmallSharpPackagesProps)"
TargetsFile="$(SmallSharpPackagesTargets)"
UsingSmallSharpSDK="$(UsingSmallSharpSDK)"
BaseIntermediateOutputPath="$(BaseIntermediateOutputPath)">
<Output TaskParameter="Packages" ItemName="FileBasedPackage" />
<Output TaskParameter="Properties" PropertyName="FileBasedProperty" />
<Output TaskParameter="Sdks" PropertyName="FileBasedSdk" />
<Output TaskParameter="Success" PropertyName="RestoreNeeded" />
<Output TaskParameter="RestoreNeeded" PropertyName="RestoreNeeded" />
</EmitTargets>
</Target>

<Target Name="RestoreBeforeBuild" BeforeTargets="ResolvePackageAssets" DependsOnTargets="EmitTargets"
Condition="'$(RestoreNeeded)' == 'true' and '$(DesignTimeBuild)' != 'true' and '$(UsingSmallSharpSDK)' != 'true'">

<PropertyGroup>
<DynamicProjectAssetsFile>$(MSBuildProjectExtensionsPath)smallsharp.assets.json</DynamicProjectAssetsFile>
</PropertyGroup>

<MSBuild
Projects="$(MSBuildProjectFullPath)"
Targets="Restore"
BuildInParallel="false"
Properties="RestoreUseStaticGraphEvaluation=false;ImportProjectExtensionProps=true;ImportProjectExtensionTargets=true;Guid=$([System.Guid]::NewGuid().ToString())">
</MSBuild>

<Copy SourceFiles="$(ProjectAssetsFile)" DestinationFiles="$(DynamicProjectAssetsFile)" />

<PropertyGroup>
<ProjectAssetsFile>$(DynamicProjectAssetsFile)</ProjectAssetsFile>
</PropertyGroup>

<Error Code="SCS001" Condition="'@(FileBasedSdk)' != '' and '$(UsingSmallSharpSDK)' != 'true'"
Text="Using #:sdk directives requires using SmallSharp as an SDK instead of a package reference: &lt;Project Sdk='SmallSharp/$(SmallSharpVersion)'&gt;" />
</Target>

<UsingTask TaskName="SortItems" TaskFactory="RoslynCodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll">
Expand Down
13 changes: 13 additions & 0 deletions src/SmallSharp/TaskItemFactory.cs
Original file line number Diff line number Diff line change
@@ -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<string, string> 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));
}
Loading