Skip to content

Commit

Permalink
[One .NET] dotnet new project and item templates
Browse files Browse the repository at this point in the history
Context: https://docs.microsoft.com/dotnet/core/tutorials/cli-templates-create-template-pack
Context: https://github.com/dotnet/templating/wiki

This implements basic Android templates that are contained in a
`Microsoft.Android.Templates.nupkg` file.

This is included in the .NET 6 Workload installers and installed to:

    C:\Program Files\dotnet\templates
    /usr/local/share/dotnet/templates

A `postinstall` action in the installers runs `dotnet new --install`, as
there does not currently appear to be any extra template functionality
for [.NET Workloads][0].

Some example project templates:

    dotnet new android            --output MyAndroidApp     --packageName com.mycompany.myandroidapp
    dotnet new androidlib         --output MyAndroidLibrary
    dotnet new android-bindinglib --output MyJavaBinding

And item templates:

    dotnet new android-activity --name LoginActivity --namespace MyAndroidApp
    dotnet new android-layout   --name MyLayout      --output Resources/layout

Note that the `android-bindinglib` template is not a special project
type. It has additional help files for writing bindings as we have in
the current Xamarin.Android templates.

I also updated the `XASdkTests` to `dotnet new` each template and
`dotnet build` the resulting output.

[0]: https://github.com/dotnet/designs/blob/bc5d2c1664299a4a640e3185e87a6b99426b6b89/accepted/2020/workloads/workloads.md
  • Loading branch information
jonathanpeppers committed Dec 2, 2020
1 parent 113ffcc commit 8710b25
Show file tree
Hide file tree
Showing 48 changed files with 538 additions and 18 deletions.
45 changes: 42 additions & 3 deletions Documentation/guides/OneDotNet.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,12 +189,45 @@ It is recommended to migrate to the new linker settings, as
There are currently a few "verbs" we are aiming to get working in
Xamarin.Android:

dotnet new
dotnet build
dotnet publish
dotnet run

Currently in .NET 5 console apps, `dotnet publish` is where all the
work to produce a self-contained "app" happens:
### dotnet new

To support `dotnet new`, we created a few basic project and item
templates for Android that are named following the patterns and naming
of existing .NET templates:

Templates Short Name Language Tags
-------------------------------------------- ------------------- ---------- ----------------------
Android Activity template android-activity [C#] Android
Android Java Library Binding android-bindinglib [C#] Android
Android Layout template android-layout [C#] Android
Android Class library androidlib [C#] Android
Android Application android [C#] Android
Console Application console [C#],F#,VB Common/Console
Class library classlib [C#],F#,VB Common/Library
Razor Page page [C#] Web/ASP.NET
ASP.NET Core Web App webapp [C#] Web/MVC/Razor Pages

To create different types of Android projects:

dotnet new android --output MyAndroidApp --packageName com.mycompany.myandroidapp
dotnet new androidlib --output MyAndroidLibrary
dotnet new android-bindinglib --output MyJavaBinding

Once the projects are created, some basic item templates can also be
used such as:

dotnet new android-activity --name LoginActivity --namespace MyAndroidApp
dotnet new android-layout --name MyLayout --output Resources/layout

### dotnet build & publish

Currently in .NET console apps, `dotnet publish` is where all the work
to produce a self-contained "app" happens:

* The linker via the `<IlLink/>` MSBuild task
* .NET Core's version of AOT, named "ReadyToRun"
Expand Down Expand Up @@ -223,12 +256,18 @@ Play, ad-hoc distribution, etc. It could be able to sign the `.apk` or
`.aab` with different keys. As a starting point, this will currently
copy the output to a `publish` directory on disk.

[illink]: https://github.com/mono/linker/blob/master/src/linker/README.md

### dotnet run

`dotnet run` can be used to launch applications on a
device or emulator via the `--project` switch:

dotnet run --project HelloAndroid.csproj

[illink]: https://github.com/mono/linker/blob/master/src/linker/README.md
Alternatively, you could use the `Run` MSBuild target such as:

dotnet build HelloAndroid.csproj -t:Run

### Preview testing

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,23 @@ public override bool Execute ()
foreach (var directory in Directory.EnumerateDirectories (packs_dir, "Microsoft.Android.*")) {
RecurseDirectory (packs_dir, packWriter, componentWriter, directory);
}
packWriter.WriteEndElement (); // </Directory> packs

// templates
var templates_dir = Path.Combine (DotNetPath, "templates");
packWriter.WriteStartElement ("Directory");
packWriter.WriteAttributeString ("Id", "templates");
packWriter.WriteAttributeString ("Name", "templates");
foreach (var directory in Directory.EnumerateDirectories (templates_dir)) {
foreach (var file in Directory.EnumerateFiles (directory, "Microsoft.Android.Templates.*.nupkg")) {
packWriter.WriteStartElement ("Directory");
packWriter.WriteAttributeString ("Id", "DOTNETTEMPLATESDIR");
packWriter.WriteAttributeString ("Name", Path.GetFileName (directory));
packWriter.WriteAttributeString ("FileSource", directory);
AddFile (templates_dir, packWriter, componentWriter, file, componentId: "ANDROIDTEMPLATES");
packWriter.WriteEndElement (); // </Directory> DOTNETTEMPLATES
}
}

packWriter.WriteEndDocument (); // </Directory>
componentWriter.WriteEndDocument (); // </ComponentGroup>
Expand Down Expand Up @@ -145,22 +162,28 @@ static void RecurseDirectory (string top_dir, XmlWriter packWriter, XmlWriter co
var fileName = Path.GetFileName (file);
if (fileName.StartsWith (".") || fileName.StartsWith ("_"))
continue;
var componentId = GetId (top_dir, file);
packWriter.WriteStartElement ("Component");
packWriter.WriteAttributeString ("Id", componentId);
packWriter.WriteStartElement ("File");
packWriter.WriteAttributeString ("Id", componentId);
packWriter.WriteAttributeString ("Name", Path.GetFileName (file));
packWriter.WriteAttributeString ("KeyPath", "yes");
packWriter.WriteEndElement (); // </File>
packWriter.WriteEndElement (); // </Component>
componentWriter.WriteStartElement ("ComponentRef");
componentWriter.WriteAttributeString ("Id", componentId);
componentWriter.WriteEndElement (); // </ComponentRef>
AddFile (top_dir, packWriter, componentWriter, file);
}
packWriter.WriteEndElement (); // </Directory>
}

static void AddFile (string top_dir, XmlWriter packWriter, XmlWriter componentWriter, string file, string componentId = null)
{
if (string.IsNullOrEmpty (componentId))
componentId = GetId (top_dir, file);
packWriter.WriteStartElement ("Component");
packWriter.WriteAttributeString ("Id", componentId);
packWriter.WriteStartElement ("File");
packWriter.WriteAttributeString ("Id", componentId);
packWriter.WriteAttributeString ("Name", Path.GetFileName (file));
packWriter.WriteAttributeString ("KeyPath", "yes");
packWriter.WriteEndElement (); // </File>
packWriter.WriteEndElement (); // </Component>
componentWriter.WriteStartElement ("ComponentRef");
componentWriter.WriteAttributeString ("Id", componentId);
componentWriter.WriteEndElement (); // </ComponentRef>
}

static string GetId (string top_dir, string path)
{
if (string.IsNullOrEmpty (path))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,17 @@
<ComponentGroupRef Id="ProductComponents" />
</Feature>
<WixVariable Id="WixUILicenseRtf" Value="LICENSE.rtf" />
<CustomAction Id="dotnet_new_install" ExeCommand="dotnet.exe new --install &quot;[#ANDROIDTEMPLATES]&quot;" Directory="dotnet" />
<CustomAction Id="dotnet_new_uninstall" ExeCommand="dotnet.exe new --uninstall Microsoft.Android.Templates" Directory="dotnet" />
<!-- For conditions see: https://stackoverflow.com/a/17608049 -->
<InstallExecuteSequence>
<Custom Action="dotnet_new_install" After="InstallFinalize">
NOT Installed
</Custom>
<Custom Action="dotnet_new_uninstall" After="InstallFinalize">
REMOVE="ALL" AND NOT UPGRADINGPRODUCTCODE
</Custom>
</InstallExecuteSequence>
</Product>

<Fragment>
Expand Down
3 changes: 3 additions & 0 deletions build-tools/create-dotnet-pkg/create-dotnet-pkg.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
<PayloadDir>$(OutputPath)\pkg\archive</PayloadDir>
<PkgOutputPath>$(OutputPath)\pkg\packages</PkgOutputPath>
<PkgResourcesPath>$(OutputPath)\pkg\resources</PkgResourcesPath>
<PkgScriptsDir>$(MSBuildThisFileDirectory)scripts</PkgScriptsDir>
<PkgDistributionDestination>$(OutputPath)\pkg\distribution.xml</PkgDistributionDestination>
<LicenseDestination>$(PkgResourcesPath)\en.lproj</LicenseDestination>
</PropertyGroup>
Expand All @@ -35,6 +36,7 @@
<_FilesToCopy Include="$(DotNetPreviewPath)\sdk-manifests\$(DotNetPreviewVersionBand)\Microsoft.NET.Workload.Android\**\*" />
<_FilesToCopy Include="$(DotNetPreviewPath)\packs\Microsoft.Android.Ref\**\*" />
<_FilesToCopy Include="$(DotNetPreviewPath)\packs\Microsoft.Android.Sdk.osx-x64\**\*" />
<_FilesToCopy Include="$(DotNetPreviewPath)\templates\**\Microsoft.Android.Templates.*.nupkg" />
</ItemGroup>
<MakeDir Directories="$(PkgOutputPath);$(PayloadDir);$(PkgResourcesPath);$(LicenseDestination)"/>
<ReplaceFileContents
Expand Down Expand Up @@ -62,6 +64,7 @@
<PkgBuildArgs Include="--identifier com.microsoft.net.workload.android.pkg" />
<PkgBuildArgs Include="--version $(AndroidPackVersionLong)"/>
<PkgBuildArgs Include="--install-location &quot;$(PkgInstallDir)&quot; "/>
<PkgBuildArgs Include="--scripts &quot;$(PkgScriptsDir)&quot; "/>
<PkgBuildArgs Include="&quot;$(PkgOutputPath)/microsoft.net.workload.android.pkg&quot; "/>
</ItemGroup>
<Exec Command="pkgbuild @(PkgBuildArgs, ' ')" />
Expand Down
6 changes: 6 additions & 0 deletions build-tools/create-dotnet-pkg/scripts/postinstall
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/bin/sh

INSTALLATION_ROOT=/usr/local/share/dotnet
NUPKG=$(find $INSTALLATION_ROOT/templates -name 'Microsoft.Android.Templates.*.nupkg')

$INSTALLATION_ROOT/dotnet new --install $NUPKG
16 changes: 16 additions & 0 deletions build-tools/create-packs/Directory.Build.targets
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
<Exec Command="$(DotNetPreviewTool) pack @(_GlobalProperties, ' ') -p:AndroidHostRID=osx-x64 &quot;$(MSBuildThisFileDirectory)Microsoft.Android.Sdk.proj&quot;" Condition=" '$(HostOS)' == 'Darwin' " />
<Exec Command="$(DotNetPreviewTool) pack @(_GlobalProperties, ' ') -p:AndroidHostRID=win-x64 &quot;$(MSBuildThisFileDirectory)Microsoft.Android.Sdk.proj&quot;" Condition=" '$(HostOS)' != 'Linux' " /> <!-- Windows pack should be built both Windows and macOS -->
<Exec Command="$(DotNetPreviewTool) pack @(_GlobalProperties, ' ') &quot;$(MSBuildThisFileDirectory)Microsoft.NET.Workload.Android.proj&quot;" />
<Exec Command="$(DotNetPreviewTool) pack @(_GlobalProperties, ' ') &quot;$(XamarinAndroidSourcePath)src\templates\templates.csproj&quot;" />
</Target>

<Target Name="ExtractWorkloadPacks"
Expand All @@ -92,6 +93,7 @@
<_WLPacks Include="$(XamarinAndroidSourcePath)bin\Build$(Configuration)\nupkgs\Microsoft.Android.Sdk.osx-x64.*.nupkg" Condition=" '$(HostOS)' == 'Darwin' " />
<_WLPacks Include="$(XamarinAndroidSourcePath)bin\Build$(Configuration)\nupkgs\Microsoft.Android.Sdk.win-x64.*.nupkg" Condition=" '$(HostOS)' == 'Windows' " />
<_WLPacks Include="$(XamarinAndroidSourcePath)bin\Build$(Configuration)\nupkgs\Microsoft.Android.Ref.*.nupkg" />
<_WLTemplates Include="$(XamarinAndroidSourcePath)bin\Build$(Configuration)\nupkgs\Microsoft.Android.Templates.*.nupkg" />
<!-- Runtime packs are not yet supported by workloads -->
<!-- <_WLPacks Include="$(XamarinAndroidSourcePath)bin\Build$(Configuration)\nupkgs\Microsoft.Android.Runtime.*.nupkg" /> -->
</ItemGroup>
Expand All @@ -106,6 +108,10 @@
SourceFiles="@(_WLPacks)"
DestinationFolder="$(DotNetPreviewPath)packs\$([System.String]::Copy('%(_WLPacks.Filename)').Replace('.$(_WLPackVersion)', ''))\$(_WLPackVersion)"
/>
<Copy
SourceFiles="@(_WLTemplates)"
DestinationFolder="$(DotNetPreviewPath)templates\$(DotNetPreviewVersionFull)"
/>
<ItemGroup>
<_UnixExecutables Include="$(DotNetPreviewPath)packs\Microsoft.Android.Sdk.*\*\tools\$(HostOS)\**\*.*" />
<_FilesToTouch Include="$(DotNetPreviewPath)sdk-manifests\$(DotNetPreviewVersionBand)\Microsoft.NET.Workload.Android\**" />
Expand All @@ -115,6 +121,11 @@
Condition=" '$(HostOS)' == 'Darwin' or '$(HostOS)' == 'Linux' "
Command="chmod +x &quot;%(_UnixExecutables.Identity)&quot;"
/>
<Exec
Command="$(DotNetPreviewTool) new --install &quot;%(_WLTemplates.FileName)%(_WLTemplates.Extension)&quot;"
WorkingDirectory="$(DotNetPreviewPath)templates\$(DotNetPreviewVersionFull)"
ContinueOnError="true"
/>
<!-- Some files had timestamps in the future -->
<Touch Files="@(_FilesToTouch)" />
<MakeDir Directories="$([System.IO.Path]::GetDirectoryName ($(_WorkloadResolverFlagFile)))" />
Expand All @@ -129,7 +140,12 @@
<_PackFilesToDelete Include="$(DotNetPreviewPath)sdk-manifests\$(DotNetPreviewVersionBand)\Microsoft.Android.Workload\**\*.*" />
<_PackFilesToDelete Include="$(DotNetPreviewPath)sdk-manifests\$(DotNetPreviewVersionBand)\Microsoft.NET.Workload.Android\**\*.*" />
<_PackFilesToDelete Include="$(DotNetPreviewPath)packs\Microsoft.Android*\**\*.*" />
<_PackFilesToDelete Include="$(DotNetPreviewPath)templates\$(DotNetPreviewVersionFull)\Microsoft.Android.Templates.*.nupkg" />
</ItemGroup>
<Exec
Command="$(DotNetPreviewTool) new --uninstall Microsoft.Android.Templates"
ContinueOnError="true"
/>
<RemoveDir Directories="%(_PackFilesToDelete.RootDir)%(_PackFilesToDelete.Directory)" />
<Delete Files="$(_WorkloadResolverFlagFile)" />
</Target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,16 @@ public void DotNetBuildLibrary (bool isRelease, bool duplicateAar)
StringAssert.Contains ("public const int MyLayout", resource_designer_text);
}

[Test]
public void DotNetNew ([Values ("android", "androidlib", "android-bindinglib")] string template)
{
var dotnet = CreateDotNetBuilder ();
Assert.IsTrue (dotnet.New (template), $"`dotnet new {template}` should succeed");
Assert.IsTrue (dotnet.New ("android-activity"), "`dotnet new android-activity` should succeed");
Assert.IsTrue (dotnet.New ("android-layout", Path.Combine (dotnet.ProjectDirectory, "Resources", "layout")), "`dotnet new android-layout` should succeed");
Assert.IsTrue (dotnet.Build (), "`dotnet build` should succeed");
}

[Test]
public void DotNetPack ([Values ("net6.0-android", "net6.0-android30")] string targetFramework)
{
Expand Down Expand Up @@ -461,6 +471,16 @@ void CreateEmptyFile (params string [] paths)
}
}

DotNetCLI CreateDotNetBuilder (string relativeProjectDir = null)
{
if (string.IsNullOrEmpty (relativeProjectDir)) {
relativeProjectDir = Path.Combine ("temp", TestName);
}
TestOutputDirectories [TestContext.CurrentContext.Test.ID] =
FullProjectDirectory = Path.Combine (Root, relativeProjectDir);
return new DotNetCLI (Path.Combine (FullProjectDirectory, $"{TestName}.csproj"));
}

DotNetCLI CreateDotNetBuilder (XASdkProject project, string relativeProjectDir = null)
{
if (string.IsNullOrEmpty (relativeProjectDir)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,18 @@ public class DotNetCLI
readonly XASdkProject project;
readonly string projectOrSolution;

public DotNetCLI (XASdkProject project, string projectOrSolution)
public DotNetCLI (string projectOrSolution)
{
this.project = project;
this.projectOrSolution = projectOrSolution;
ProjectDirectory = Path.GetDirectoryName (projectOrSolution);
}

public DotNetCLI (XASdkProject project, string projectOrSolution)
: this (projectOrSolution)
{
this.project = project;
}

/// <summary>
/// Runs the `dotnet` tool with the specified arguments.
/// </summary>
Expand Down Expand Up @@ -73,6 +78,16 @@ protected bool Execute (params string [] args)
return succeeded;
}

public bool New (string template, string output = null)
{
var arguments = new List<string> {
"new",
template,
"--output", $"\"{output ?? ProjectDirectory}\"",
};
return Execute (arguments.ToArray ());
}

public bool Build (string target = null, string [] parameters = null)
{
var arguments = GetDefaultCommandLineArgs ("build", target, parameters);
Expand Down Expand Up @@ -123,14 +138,16 @@ List<string> GetDefaultCommandLineArgs (string verb, string target = null, strin
var arguments = new List<string> {
verb,
$"\"{projectOrSolution}\"",
$"/p:Configuration={project.Configuration}",
"/noconsolelogger",
$"/flp1:LogFile=\"{BuildLogFile}\";Encoding=UTF-8;Verbosity={Verbosity}",
$"/bl:\"{Path.Combine (testDir, "msbuild.binlog")}\""
};
if (!string.IsNullOrEmpty (target)) {
arguments.Add ($"/t:{target}");
}
if (project != null) {
arguments.Add ($"/p:Configuration={project.Configuration}");
}
if (Directory.Exists (AndroidSdkPath)) {
arguments.Add ($"/p:AndroidSdkDirectory=\"{AndroidSdkPath}\"");
}
Expand Down
15 changes: 15 additions & 0 deletions src/templates/Directory.Build.targets
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<Project>
<Import Project="..\..\build-tools\scripts\XAVersionInfo.targets" />
<PropertyGroup>
<BeforePack>
_GetDefaultPackageVersion;
$(BeforePack);
</BeforePack>
</PropertyGroup>
<Target Name="_GetDefaultPackageVersion"
DependsOnTargets="GetXAVersionInfo" >
<PropertyGroup>
<PackageVersion>$(AndroidPackVersionLong)+sha.$(XAVersionHash)</PackageVersion>
</PropertyGroup>
</Target>
</Project>
25 changes: 25 additions & 0 deletions src/templates/android-activity/.template.config/template.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"$schema": "http://json.schemastore.org/template",
"author": "Microsoft",
"classifications": [ "Android" ],
"name": "Android Activity template",
"description": "An Android Activity class",
"tags": {
"language": "C#",
"type": "item"
},
"identity": "Microsoft.Android.AndroidActivity",
"shortName": "android-activity",
"sourceName": "Activity1",
"primaryOutputs": [
{ "path": "Activity1.cs" }
],
"defaultName": "Activity1",
"symbols": {
"namespace": {
"description": "namespace for the generated code",
"replaces": "AndroidApp1",
"type": "parameter"
}
}
}
18 changes: 18 additions & 0 deletions src/templates/android-activity/Activity1.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using Android.App;
using Android.OS;
using Android.Runtime;
using Android.Widget;

namespace AndroidApp1
{
[Activity(Label = "@string/app_name", MainLauncher = true)]
public class Activity1 : Activity
{
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);

// Create your application here
}
}
}
Loading

0 comments on commit 8710b25

Please sign in to comment.