diff options
Diffstat (limited to 'Documentation/coding-guidelines')
-rw-r--r-- | Documentation/coding-guidelines/package-projects.md | 228 | ||||
-rw-r--r-- | Documentation/coding-guidelines/project-guidelines.md | 17 |
2 files changed, 241 insertions, 4 deletions
diff --git a/Documentation/coding-guidelines/package-projects.md b/Documentation/coding-guidelines/package-projects.md new file mode 100644 index 0000000000..e0d68894b4 --- /dev/null +++ b/Documentation/coding-guidelines/package-projects.md @@ -0,0 +1,228 @@ +# Package projects +Package projects bring together all the assemblies that make up a library on different platforms into a set of NuGet packages. + +## Package heirarchy +All libraries should have at least one package if they represent public surface area. This is called the *reference package* and is named the same as the library's assembly, EG: System.Collections.Immutable. + +Packages may have platform specific implementation packages. These are referred to as *runtime packages* and follow the naming convention of runtime.{rid}.{assemblyName}, EG: runtime.unix.System.IO.FileSystem. + +In either case the file name of the `.pkgproj` is just {assemblyName}.pkgproj and package names are derived from the contents of the project. + +## Package samples +### Simple portable library +This is the simplest case. The package project need only reference the single project that implements the portable libary. + +Sample `System.Text.Encodings.Web.pkgproj` +``` +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" /> + + <ItemGroup> + <ProjectReference Include="..\src\System.Text.Encodings.Web.csproj"> + <SupportedFramework>net45;netcore45;wp8;wpa81;dnxcore50</SupportedFramework> + </ProjectReference> + </ItemGroup> + + <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" /> +</Project> +``` + +### Portable library, inbox on some platforms +These packages need to include placeholders for inbox platforms. They should also include reference assemblies for representing the fixed API that is inbox in old platforms. + +Sample `System.Collections.Concurrent.pkgproj` +``` +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" /> + <ItemGroup> + <ProjectReference Include="..\ref\4.0.0\System.Collections.Concurrent.depproj"> + <SupportedFramework>net45;netcore45;wpa81</SupportedFramework> + </ProjectReference> + <ProjectReference Include="..\ref\System.Collections.Concurrent.csproj"> + <SupportedFramework>net46;netcore50;dnxcore50</SupportedFramework> + </ProjectReference> + <ProjectReference Include="..\src\System.Collections.Concurrent.csproj"/> + + <InboxOnTargetFramework Include="MonoAndroid10" /> + <InboxOnTargetFramework Include="MonoTouch10" /> + <InboxOnTargetFramework Include="net45" /> + <InboxOnTargetFramework Include="win8" /> + <InboxOnTargetFramework Include="wpa81" /> + <InboxOnTargetFramework Include="xamarinios10" /> + <InboxOnTargetFramework Include="xamarinmac20" /> + </ItemGroup> + <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" /> +</Project> +``` + +### Framework-specific library +Framework specific libraries are effectively the same as the previous example. The difference is that the src project reference **must** refer to the `.builds` file which will provide multiple assets from multiple projects. + +### Platform-specific library +These packages need to provide a different platform specific implementation on each platform. They do this by splitting the implementations into seperate packages and associating those platform specific packages with the primary reference package. Each platform specific package sets `PackageTargetRuntime` to the specific platform RID that it applies. + +Sample `System.IO.FileSystem.pkgproj` +``` +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" /> + <ItemGroup> + <ProjectReference Include="..\ref\System.IO.FileSystem.csproj"> + <SupportedFramework>net46;netcore50;dnxcore50</SupportedFramework> + </ProjectReference> + <ProjectReference Include="..\src\Facade\System.IO.FileSystem.csproj" /> + <ProjectReference Include="win\System.IO.FileSystem.pkgproj" /> + <ProjectReference Include="unix\System.IO.FileSystem.pkgproj" /> + + <InboxOnTargetFramework Include="MonoAndroid10" /> + <InboxOnTargetFramework Include="MonoTouch10" /> + <InboxOnTargetFramework Include="xamarinios10" /> + <InboxOnTargetFramework Include="xamarinmac20" /> + </ItemGroup> + <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" /> +</Project> +``` + +`win/System.IO.FileSystem.pkgproj` +``` +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" /> + + <PropertyGroup> + <PackageTargetRuntime>win7</PackageTargetRuntime> + <PreventImplementationReference>true</PreventImplementationReference> + </PropertyGroup> + + <ItemGroup> + <ProjectReference Include="..\..\src\System.IO.FileSystem.builds"> + <AdditionalProperties>OSGroup=Windows_NT</AdditionalProperties> + </ProjectReference> + + <!-- No implementation on platforms where our P-Invokes are not allowed --> + <NotSupportedOnTargetFramework Include="win8" /> + <NotSupportedOnTargetFramework Include="wp8" /> + <NotSupportedOnTargetFramework Include="wpa81" /> + + <!-- don't use the dotnet implementation for any version of desktop, it's implementation comes from the reference package --> + <ExternalOnTargetFramework Include="net" /> + </ItemGroup> + + + <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" /> +</Project> +``` +`unix/System.IO.FileSystem.pkgproj` +``` +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" /> + + <PropertyGroup> + <PackageTargetRuntime>unix</PackageTargetRuntime> + <PreventImplementationReference>true</PreventImplementationReference> + </PropertyGroup> + + <ItemGroup> + <ProjectReference Include="..\..\src\System.IO.FileSystem.builds"> + <AdditionalProperties>OSGroup=Linux</AdditionalProperties> + </ProjectReference> + </ItemGroup> + + <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" /> +</Project> +``` + +## Asset selection +The makeup of a package folder is primarily a grouping of project references to the projects that compose that package. Settings within each referenced project determines where that asset will be placed in the package. For example, reference assembly projects will be placed under the `ref/{targetMoniker}` folder in the package and implementations will be under either `lib/{targetMoniker}` or `runtimes/{rid}/lib/{targetMoniker}`. Whenever NuGet evaulates a package in the context of a referencing project it will choose the best compile time asset (preferring `ref`, then falling back to `lib`) and runtime asset (preffering `runtimes/{rid}/lib` and falling back to `lib`) for every package that is referenced. For more information see http://docs.nuget.org/. + +Asset projects (`.csproj`, `.vbproj`, or `.depproj`) can control their `{targetMoniker}` using the `PackageTargetFramework` property in the project file. Similarly `{rid}` is controlled using the `PackageTargetRuntime` property. In the corefx repo we automatically select default values for these properties based on the [Build pivots](#build-pivots). These can be overridden in the project reference using metadata of the same name, but this is rarely needed. + +The primary thing that the library author needs to do in order to ensure the correct asset selection is: +1. Configure the correct projects in your library's `.builds` file. +2. Reference the `.builds` file from the package project. +3. Provide a default PackageTargetFramework for empty-TargetGroup builds. +``` +<PackageTargetFramework Condition="'$(PackageTargetFramework)' == ''">dotnet5.4</PackageTargetFramework> +``` + +### Desktop facades +Desktop facades must be part of the reference package. This is because if we were to use the reference assembly on desktop it would have type collisions with whatever types already exist in the desktop reference assemblies. Since we include the desktop reference facade in the reference package we also include the runtime facade in the same package for compression savings. + +## Applicability validation +Part of package build is to ensure that a package is applicable on all platforms it supports and not applicable on platforms it does not support. We do this validation for a set of targets established in the packaging tools (see [DefaultValidateFramework](https://github.com/dotnet/buildtools/blob/9f4ddda1cb021c9bd25f606bc4e74b92e4b82869/src/Microsoft.DotNet.Build.Tasks.Packaging/src/PackageFiles/Packaging.targets#L709)). Package projects identify the targets supported in one of two ways. +1. **Preferred:** Through `SupportedFramework` metadata on the project reference. The metadata will associate the API version of that project reference with the frameworks listed. +``` +<ProjectReference Include="..\ref\4.0.0\System.Collections.Concurrent.depproj"> + <SupportedFramework>net45;netcore45;wpa81</SupportedFramework> +</ProjectReference> +<ProjectReference Include="..\ref\System.Collections.Concurrent.csproj"> + <SupportedFramework>net46;netcore50;dnxcore50</SupportedFramework> +</ProjectReference> +``` + +2. Through SupportedFramework items with Version metdata. +``` +<!-- no version indicates latest is supported --> +<SupportedFramework Include="net46;netcore50;dnxcore50" /> +<!-- specific version indicates that version is supported --> +<SupportedFramework Include="net45;netcore45;wpa81"> + <Version>4.0.0.0</Version> +</SupportedFramework> +``` + +###Inbox assets +Some libraries are supported inbox on particular frameworks. For these frameworks the package should not present any assets for (ref or lib) for that framework, but instead permit installation and provide no assets. We do this in the package by using placeholders ref and lib folders for that framework. In the package project one can use `InboxOnTargetFramework` items. The following is an example from the System.Linq.Expressions package. +``` +<InboxOnTargetFramework Include="net45" /> +<InboxOnTargetFramework Include="win8" /> +<InboxOnTargetFramework Include="wp80" /> +<InboxOnTargetFramework Include="wpa81" /> +``` + +If the library is also a "classic" reference assembly, not referenced by default, then adding the `AsFrameworkReference` metadata will instruct that the package include a `frameworkReference` element in the nuspec. The following is the an example from the Microsoft.CSharp package. +``` +<InboxOnTargetFramework Include="net45"> + <AsFrameworkReference>true</AsFrameworkReference> +</InboxOnTargetFramework> +<InboxOnTargetFramework Include="win8" /> +<InboxOnTargetFramework Include="wp80" /> +<InboxOnTargetFramework Include="wpa81" /> +``` + +Package validation will catch a case where we know a library is supported inbox but a package is using an asset from the package. This data is driven by framework lists from previously-shipped targeting packs. The error will appear as: *Framework net45 should support Microsoft.CSharp inbox but {explanation of problem}. You may need to add <InboxOnTargetFramework Include="net45" /> to your project.* + +###External assets +Runtime specific packages are used to break apart implementations into seperate packages and enable "pay-for-play". For example: don't download the Windows implementation if we're only building/deploying for linux. In most cases we can completely seperate implementations into seperate packages such that they easily translate. For example: +``` +runtimes/win/lib/dotnet5.4/System.Banana.dll +runtimes/unix/lib/dotnet5.4/System.Banana.dll +``` +This can easily be split into a `win` and `unix` package. If someone happens to install both packages into a project they'll still get a single implementation. + +Consider the following: +``` +runtimes/win/lib/dotnet5.4/System.Banana.dll +runtimes/win/lib/net46/System.Banana.dll +``` +Suppose we wanted to split the desktop (`net46`) implementation into a seperate package than the portable implementation. Doing so would cause both the `dotnet5.4` asset and the `net46` asset to be applicable and result in a bin-clash. This is because in a single package the `net46` asset is preferred over the `dotnet5.4` asset, but in seperate packages both are in view. The packaging validation will catch this problem and display an error such as + +*System.Banana includes both package1/runtimes/win/lib/net46/System.Banana.dll and package2/runtimes/win/lib/dotnet5.4/System.Banana.dll an on net46 which have the same name and will clash when both packages are used.* + + +The fix for the error is to put a placeholder in the package that contains the asset we want to prevent applying. This can be done with the following syntax. +``` +<ExternalOnTargetFramework Include="net46" /> +``` + +###Not supported +In rare cases a particular library might represent itself as targeting a specific portable moniker (eg: `dotnet5.4`) but it cannot be supported on a particular target framework that is included in that portable moniker for other reasons. One example of this is System.Diagnostics.Process. The surface area of this API is portable to dotnet5.4 and could technically run in UWP based on its managed dependencies. The native API, however, is not supported in app container. To prevent this package and packages which depend on from installing in UWP projects, only to fail at runtime, we can block the package from being installed. + +To do this we create a placeholder in the lib folder with the following syntax. The resulting combination will be an applicable ref asset with no applicable lib and NuGet's compat check will fail. +``` +<NotSupportedOnTargetFramework Include="net46" /> +``` +The packaging validation will catch this problem and display an error such as +*System.Diagnostics.Process should not be supported on netcore50 but has both compile and runtime assets.*
\ No newline at end of file diff --git a/Documentation/coding-guidelines/project-guidelines.md b/Documentation/coding-guidelines/project-guidelines.md index a28923aa2c..960d218556 100644 --- a/Documentation/coding-guidelines/project-guidelines.md +++ b/Documentation/coding-guidelines/project-guidelines.md @@ -1,12 +1,16 @@ #Library Project guidelines Library projects should use the fllowing directory layout. ``` -src\<Library Name>\src - Contains the source code for the library. -src\<Library Name>\ref - Contains any refernce assembly projects for the library +src\<Library Name>\src - Contains the source code for the library. +src\<Library Name>\ref - Contains any reference assembly projects for the library +src\<Library Name>\pkg - Contains package projects for the library. src\<Library Name>\tests - Contains the test code for a library ``` -In the src directory for a library there should be only **one** `.csproj` file that contains any information necessary to build the library in various configurartions (see [Configurations](#project-configuration-conventions)). The src directory should also contain exactly **one** `.builds` file which contains all the valid configurations that a library should be built for (see [#.builds file](#project-builds-file)). In some cases there might be a Facade subdirectory under src with a .csproj (see [Facades Projects](#facades-projects)). +In the src directory for a library there should be only **one** `.csproj` file that contains any information necessary to build the library in various configurartions (see [Configurations](#project-configuration-conventions)). The src directory should also contain exactly **one** `.builds` file which contains all the valid configurations that a library should be built for (see [#.builds file](#project-builds-file)). In some cases there might be a Facade subdirectory under src with a .csproj (see [Facades projects](#facades-projects)). +In the ref directory for the library there should be at most **one** `.csproj` that contains the latest API for the reference assembly for the library. If a library cannot support all supported targets using the latest API it should use `.depproj` projects for each significant historical version in order to download and redistribute the historical reference assembly in the latest package. If a library is a pure portable library with a single implementation it need not use a reference assembly at all. (see [Reference assembly projects](#reference-assembly-projects)). + +In the pkg directory for the library there should be only **one** `.pkgproj` for the primary package for the library. If the library has platform-specific implementations those should be split into platform specific projects in a subfolder for each platform. (see [Package projects](./package-projects.md)) ##Build Pivots Below is a list of all the various options we pivot the project builds on. @@ -91,7 +95,7 @@ Below is an example for System.Linq.Expressions where it only builds for the win <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.traversal.targets))\dir.traversal.targets" /> </Project> ``` -##Facades Projects +##Facades projects Facade projects are unique in that they don't have any code and instead are generated by finding a contract reference assembly with the matching identity and generating type forwards for all the types to where they live in the implementation assemblies (aka facade seeds). There are also partial facades which contain some type forwards as well as some code definitions. Ideally all the various build configurations would be contained in the one csproj file per library but given the unique nature of facades and the fact they are usually a complete fork of everything in the project file it is recommended to create a matching csproj under a Facade directory (<library>\src\Facade\<library>.csproj) and reference that from your .builds file, as in the System.Linq.Expressions example. TODO: Fill in more information about the required properties for creatng a facade project. @@ -120,3 +124,8 @@ As mentioned in [Conventions for forked code](conventions-for-forked-code) `#ifd - Defines based on conventions should be one of `$(OSGroup)`, `$(TargetGroup)`, `$(ConfigurationGroup)`, or `$(Platform)`, matching exactly by case to ensure consistency. - Examples: `<DefineConstants>$(DefineConstants),netcore50</DefineContants>` - Defines based on convention should match the pattern `FEATURE_<feature name>`. These can unique to a given library project or potentially shared (via name) across multiple projects. + +# Reference assembly projects +Reference assemblies are required for any library that has more than one implementation or uses a facade. A reference assembly is a surface-area-only assembly that represents the public API of the library. To generate a reference assembly source file you can use the [GenAPI tool](https://www.nuget.org/packages/Microsoft.DotNet.BuildTools.GenAPI). + +When adding API to a library it is sometimes impossible to support the new API on all the platforms where the library is supported today. We strive to support new APIs everywhere, but sometimes this is not possible. For example: string is part of the core library so a new property on string must exist on the core library type. We cannot ship a new copy of string to the desktop due to limitations in binding rules for the desktop. Instead of dropping support for desktop we include an older version of the reference assembly that represents the smaller surface area for that version of desktop. This is achieved by adding a deployment project (`.depproj`) to deploy the old reference assembly from the old package. |