11 KiB
Contributing
This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com.
When you submit a pull request, a CLA bot will automatically determine whether you need to provide a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions provided by the bot. You will only need to do this once across all repos using our CLA.
You can contribute to this project by contributing to:
If you intend to contribute code changes, learn how to set up your development environment.
When contributing code changes, validate your changes by rebuilding the winmd and then inspecting the reported winmd diff to ensure all changes were intentional:
This project has adopted the Microsoft Open Source Code of Conduct. For more information see the Code of Conduct FAQ or contact opencode@microsoft.com with any additional questions or comments.
Set up your development environment
- Install Visual Studio Code
- Install PowerShell
- Install ILSpy
- Clone the repo
- Run a full build
Namespaces
Our tooling organizes Win32 APIs into namespaces. This provides an alternative way for developers to easily include large areas of functionality and improves consistency and discoverability across Win32 and WinRT.
- Assign a header file to a single namespace
- Split a header file among multiple namespaces
- Refactoring namespaces
Assign a header file to a single namespace
The simplest way to assign an API to a namespace is to associate the API's header file with a partition. A header file can only be associated with one partition, and by default all APIs included in the header file will be added to the partition's target namespace.
Partitions are defined as folders with main.cpp and settings.rsp files.
- main.cpp contains
#include
statements like you would use to call the APIs directly. This typically includes the header files to be associated with the partition as well as any other dependent headers, included in the proper order. - settings.rsp associates a list of header files to a namespace. Reference existing partitions to understand the template for this file. The important sections are
--traverse
, which lists the header files to include, and--namespace
which lists the namespace to associate with the content of those header files. Note that headers should be listed alphabetically by convention, and the casing needs to match the casing of the filenames.
You can test localized changes to a partition by running ./scripts/GenerateMetadataSourceForPartition.ps1 <PARTITION>
from the repo root. If it compiles, the changes are likely correct. A common reason for failure is main.cpp either doesn't include all the necessary dependent headers needed to use the target headers or doesn't include them in the proper order.
Split a header file among multiple namespaces
If a header file doesn't cleanly map to one namespace, it should be associated with a partition and namespace that makes sense for the majority of its APIs (using the steps above for a single namespace), and then the rest of the APIs should be manually remapped using requiredNamespacesForNames.rsp. This file contains one line per API and follows the format <API>=<NAMESPACE>
. It is a single file that is shared across all partitions.
For maintainability, it is important to keep requiredNamespacesForNames.rsp organized with APIs grouped by header files. APIs should be added within # region <HEADER>
sections based on the header files where they are defined. APIs within a # region
should be sorted alphabetically by selecting them and then using Visual Studio Code's Sort Lines Ascending
command.
Refactoring namespaces
Note that when refactoring namespaces, requiredNamespacesForNames.rsp will take precedence over any namespaces declared in the partitions, so make sure it doesn't contain remappings that will conflict with the expected factoring from the partitions. For example, if you create a new Registry partition to assign everything in winreg.h to Windows.Win32.System.Registry, but requiredNamespacesForNames.rsp was previously updated to map Reg* APIs to a different namespace, you won't achieve the desired result unless you remove the Reg* entries from requiredNamespacesForNames.rsp.
Other files that need to be kept in sync when refactoring namespaces include manual and autotypes.rsp, which manually define some types, as well as header.txt, which adds using statements to generated .cs files to resolve cross-namespace references.
Enums
Our tooling produces enums for loosely typed parameters and fields that expect a closed set of values. This improves discoverability and usability of the values in the projections.
Enums are defined in enums.json. This file provides a schema for extracting constants and macros from headers and emitting them in the winmd as an enum. Each object in the JSON array is an enum with the following properties:
namespace
- Optional property indicating the namespace for the enum- Note: If omitted, the enum will inherit the namespace of the first entry in
uses
- Note: If omitted, the enum will inherit the namespace of the first entry in
type
- Optional property indicating the type for the enum- Note: If omitted, the default value is
uint
- Note: If omitted, the default value is
name
- The name of the enumflags
- Whether this is a flags enumautoPopulate
- For automatically defined enums, rules for how to populate themheader
- The header to scanfilter
- The constant or macro prefix to search for inheader
members
- For manually defined enums, a list of membersname
- The name of the enum membervalue
- The value of the enum member
uses
- A list of APIs where this enum is usedmethod
- The method nameparameter
- The parameter name ofmethod
struct
- The struct namefield
- The field name ofstruct
In the example below, a flags enum called WNDCLASS_STYLES
is created with all of the CS_
constants from winuser.h
. This enum is then used by the style fields of all the WNDCLASS
structs. Since no namespace
or type
are specified, the enum will live in the same namespace as WNDCLASSA
and will be of type uint
.
{
"name": "WNDCLASS_STYLES",
"flags": true,
"autoPopulate": {
"filter": "CS_",
"header": "WinUser.h"
},
"members": [],
"uses": [
{
"struct": "WNDCLASSA",
"field": "style"
},
{
"struct": "WNDCLASSW",
"field": "style"
},
{
"struct": "WNDCLASSEXA",
"field": "style"
},
{
"struct": "WNDCLASSEXW",
"field": "style"
}
]
}
You can add new enums, modify existing enums, or apply enums to more APIs by modifying enums.json.
Validating changes
Full builds
The simplest but slowest way to validate changes is to perform a full build with ./DoAll.ps1
and then inspect the reported winmd diff to ensure all changes were intentional. A full build can take 25-30 minutes. Add -Clean
to perform a clean build.
Incremental builds
If you have already performed a full build and are making incremental changes, you can effectively perform incremental builds by running ./scripts/GenerateMetadataSourceForPartition.ps1 <PARTITION>
for each impacted <PARTITION>
, and then build and test the winmd by running ./scripts/BuildMetadataBin.ps1 && ./scripts/TestWinmdBinary.ps1
. If you are only making changes to the emitter, then you don't need to regenerate the partitions and can just rebuild the winmd.
Note that stale artifacts on your system may sometimes result in cryptic errors when attempting incremental builds. If you do encounter cryptic errors during incremental builds that you suspect are the result of previously built changes, reset your system state by running a clean build with ./DoAll.ps1 -Clean
.
Comparing against the baseline
A baseline winmd is checked into the repo which is used to report diffs introduced by changes. These diffs are reported by ./scripts/TestWinmdBinary.ps1
which is called during both full and incremental builds if you follow the steps above.
When validating changes, it's important to evaluate the diffs to ensure all changes are intentional. Common patterns to expect in the diffs include:
- APIs were added to the baseline
- APIs were removed from the baseline
- APIs were moved to different namespaces
Additionally, it is useful to load the winmd in ILSpy and navigate through the APIs as another means to identify additional changes that may be required to achieve the desired end result. You may notice that two related APIs are in different namespaces or that a type that an API depends on was not moved as you would have expected. If that happens, search the repo for the API or its header file to identify where it may be being mapped to another namespace.
Once all the changes are validated, update the baseline by following the steps reported in the build output.