TODO: This section needs to be driven by code in the Core library.
API Implementation
This section describes guidelines for implementing Azure SDK client libraries. Please note that some of these guidelines are automatically enforced by code generation tools.
Service Client
When configuring your client library, particular care must be taken to ensure that the consumer of your client library can properly configure the connectivity to your Azure service both globally (along with other client libraries the consumer is using) and specifically with your client library.
TODO: add a brief mention of the approach to implementing service clients.
Service Methods
TODO: Briefly introduce that service methods are implemented via an
HttpPipeline
instance. Mention that much of this is done for you using code generation.
HttpPipeline
The following example shows a typical way of using HttpPipeline
to implement a service call method. The HttpPipeline
will handle common HTTP requirements such as the user agent, logging, distributed tracing, retries, and proxy configuration.
TODO: Show an example of invoking the pipeline
HttpPipelinePolicy/Custom Policies
The HTTP pipeline includes a number of policies that all requests pass through. Examples of policies include setting required headers, authentication, generating a request ID, and implementing proxy authentication. HttpPipelinePolicy
is the base type of all policies (plugins) of the HttpPipeline
. This section describes guidelines for designing custom policies.
TODO: Show how to customize a pipeline
Service Method Parameters
TODO: This section needs to be driven by code in the Core library.
Parameter Validation
In addition to general parameter validation guidelines:
TODO: Briefly show common patterns for parameter validation
Supporting Types
TODO: This section needs to be driven by code in the Core library.
Serialization
TODO: This section needs to be driven by code in the Core library.
JSON Serialization
TODO: This section needs to be driven by code in the Core library.
Enumeration-like Structs
As described in general enumeration guidelines, you should use enum
types whenever passing or deserializing a well-known set of values to or from the service.
There may be times, however, where a struct
is necessary to capture an extensible value from the service even if well-known values are defined,
or to pass back to the service those same or other user-supplied values:
- The value is retrieved and deserialized from service, and may contain a value not supported by the client library.
- The value is roundtripped: the value is retrieved and deserialized from the service, and may later be serialized and sent back to the server.
TODO: Content in this section may need a new home.
✅ DO name enum class
es and enumerators using PascalCase.
✅ DO use enum class
for enumerations. For example:
enum class PinState {
Off,
On
};
Using Azure Core Types
Implementing Subtypes of Operation<T>
Subtypes of Operation<T>
are returned from service client methods invoking long running operations.
✅ DO check the value of IsDone
in subclass implementations of PollInternal
and PollUntilDoneInternal
and immediately return the result of GetRawResponse
if it is true.
TODO: Show an example implementation for Operation
.
✅ DO throw from methods on Operation<T>
subclasses in the following scenarios.
- If an underlying service operation call from
Poll
orPollUntilDone
throws, re-throwRequestFailedException
or its subtype. - If the operation completes with a non-success result, throw
RequestFailedException
or its subtype fromPoll
orPollUntilDone
.- Include any relevant error state information in the exception message.
TODO: Show an example of how to handle errors.
- If the
Value
property is evaluated after the operation failed (HasValue
is false andIsDone
is true) throw the same exception as the one thrown when the operation failed.
TODO: Show an example of how to throw in this case.
- If the
Value
property is evaluated before the operation is complete (IsDone
is false) throwTODO: What to throw
.- The exception message should be: “The operation has not yet completed.”
TODO: DO we want this behavior.
TODO: Show an example of how to throw in this case.
SDK Feature Implementation
Configuration
TODO: This section needs to be driven by code in the Core library.
Logging
Request logging will be done automatically by the HttpPipeline
. If a client library needs to add custom logging, follow the same guidelines and mechanisms as the pipeline logging mechanism. If a client library wants to do custom logging, the designer of the library must ensure that the logging mechanism is pluggable in the same way as the HttpPipeline
logging policy.
✅ DO follow the logging section of the Azure SDK General Guidelines if logging directly (as opposed to through the HttpPipeline
).
C++ Logging specific details
TODO: This additional logging info may need a new home.
Client libraries must support robust logging mechanisms so that the consumer can adequately diagnose issues with the method calls and quickly determine whether the issue is in the consumer code, client library code, or service.
In general, our advice to consumers of these libraries is to establish logging in their preferred manner at the WARNING
level or above in production to capture problems with the application, and this level should be enough for customer support situations. Informational or verbose logging can be enabled on a case-by-case basis to assist with issue resolution.
✅ DO use the Azure Core library for logging.
✅ DO support pluggable log handlers.
✅ DO make it easy for a consumer to enable logging output to the console. The specific steps required to enable logging to the console must be documented.
✅ DO use one of the following log levels when emitting logs: Verbose
(details), Informational
(things happened), Warning
(might be a problem or not), and Error
.
✅ DO use the Error
logging level for failures that the application is unlikely to recover from (out of memory, etc.).
✅ DO use the Warning
logging level when a function fails to perform its intended task. This generally means that the function will raise an exception. Do not include occurrences of self-healing events (for example, when a request will be automatically retried).
✔️ YOU MAY log the request and response (see below) at the Warning
when a request/response cycle (to the start of the response body) exceeds a service-defined threshold. The threshold should be chosen to minimize false-positives and identify service issues.
✅ DO use the Informational
logging level when a function operates normally.
✅ DO use the Verbose
logging level for detailed troubleshooting scenarios. This is primarily intended for developers or system administrators to diagnose specific failures.
✅ DO only log headers and query parameters that are in a service-provided “allow-list” of approved headers and query parameters. All other headers and query parameters must have their values redacted.
✅ DO log request line and headers as an Informational
message. The log should include the following information:
- The HTTP method.
- The URL.
- The query parameters (redacted if not in the allow-list).
- The request headers (redacted if not in the allow-list).
- An SDK provided request ID for correlation purposes.
- The number of times this request has been attempted.
✅ DO log response line and headers as an Informational
message. The format of the log should be the following:
- The SDK provided request ID (see above).
- The status code.
- Any message provided with the status code.
- The response headers (redacted if not in the allow-list).
- The time period between the first attempt of the request and the first byte of the body.
✅ DO log an Informational
message if a service call is cancelled. The log should include:
- The SDK provided request ID (see above).
- The reason for the cancellation (if available).
✅ DO log exceptions thrown as a Warning
level message. If the log level set to Verbose
, append stack trace information to the message.
Distributed Tracing
TODO: Add guidance for distributed tracing implementation
Telemetry
TODO: Add guidance regarding user agent strings
Testing
We believe testing is a part of the development process, so we expect unit and integration tests to be a part of the source code. All components must be covered by automated testing, and developers should strive to test corner cases and main flows for every use case.
All code should contain, at least, requirements, unit tests, end-to-end tests, and samples.
Tests should be written using the [Google Test][] library.
Language-specific other
Unlike many other programming languages, which have large runtimes, the C++ standard runtime is limited in functionality and scope. The standard library covers areas such as memory and string manipulation, standard input/output, floating point and others. However, many of the features required for modern applications and services; e.g. those required for networking and advanced memory management are not part of the standard library. Instead, many of those features are included in open source C++ libraries that are also cross-platform with good support for Windows, OSX and most Linux platforms. Because of that support and because Azure SDK implementations will need such functionality, it is expected that client libraries will take dependencies on these libraries. Ensure the version matches to allow for compatibility when an application integrates multiple client libraries.
✅ DO utilize the following libraries as needed for commonly required operations:
Library | Version | Usage |
---|---|---|
libxml2 | 2.7.6 | XML parser - http://www.xmlsoft.org/ |
gtest | 1.10.0 | Google Test - https://github.com/google/googletest/ |
Note: None of the libraries in this list currently throw C++ exceptions; policy for propagation of C++ exceptions from dependencies needs to be evaluated on a dependency-by-dependency basis.
⛔️ DO NOT use implicit assignment inside a test. This is generally an accidental omission of the second =
of the logical compare. The following is confusing and prone to error.
if (a = b) { ... }
Does the programmer really mean assignment here? Sometimes yes, but usually no. Instead use explicit tests and avoid assignment with an implicit test. The recommended form is to do the assignment before doing the test:
a = b;
if (a) { ... }
⛔️ DO NOT use the register keyword. Modern compilers will put variables in registers automatically.
✅ DO be const
correct. C++ provides the const
keyword to allow passing as parameters objects that cannot change to indicate when a method doesn’t modify its object. Using const
in all the right places is called “const correctness.”
✅ DO use #if
instead of #ifdef
. For example:
// Bad example
#ifdef DEBUG
TemporaryDebuggerBreak();
#endif
Someone else might compile the code with turned-of debug info like:
cc -c lurker.cc -DDEBUG=0
Alway use #if
if you have to use the preprocessor. This works fine, and does the right thing, even if DEBUG
is not defined at all (!)
// Good example
#if DEBUG
TemporaryDebuggerBreak();
#endif
If you really need to test whether a symbol is defined or not, test it with the defined()
construct, which allows you to add more things later to the conditional without editing text that’s already in the program:
#if !defined(USER_NAME)
#define USER_NAME "john smith"
#endif
✅ DO
Use #if
to comment out large code blocks.
Sometimes large blocks of code need to be commented out for testing. The easiest way to do this is with an #if 0
block:
void Example()
{
great looking code
#if 0
many lines of code
#endif
more code
}
You can’t use /**/
style comments because comments can’t contain comments and a large block of your code will probably contain connects.
Do not use #if 0
directly. Instead, use descriptive macro names:
#if NOT_YET_IMPLEMENTED
#if OBSOLETE
#if TEMP_DISABLED
Always add a short comment explaining why it is commented out.
⛔️ DO NOT put data definitions in header files. For example, this should be avoided:
/* aheader.hpp */
int x = 0;
It’s bad magic to have space consuming code silently inserted through the innocent use of header files. It’s not common practice to define variables in the header file, so it will not occur to developers to look for this when there are problems. Instead, define the variable once in a source file and then use an extern
statement to reference it in the header file.
⛔️ DO NOT use magic numbers. A magic number is a bare naked number used in source code. It’s magic because no-one will know what it means after a while. This significantly reduces maintainability. For example:
// Don't write this.
if (19 == foo) { RefundLotsMoney(); }}
else { HappyDaysIKnowWhyIAmHere(); }
Instead of magic numbers, use a real name that means something. You can use constexpr
for names. For example:
constexpr int WE_GOOFED = 19;
if (WE_GOOFED == foo) { RefundLotsMoney(); }
else { HappyDaysIKnowWhyIAmHere(); }
✅ DO check every system call for an error return, unless you know you wish to ignore errors. For example, printf
returns an error code but it is rarely relevant. Cast the return to (void) if you do not care about the error code.
(void)printf("The return value is ignored");
✅ DO include the system error text when reporting system error messages.
Complexity Management
☑️ YOU SHOULD Initialize all variables. Only leave them uninitialized if there is a real performance reason to do so. Use static and dynamic analysis tools to check for uninitialized access. You may leave “result” variables uninitialized so long as they clearly do not escape from the innermost lexical scope.
☑️ YOU SHOULD limit function bodies to one page of code (40 lines, approximately).
✅ DO document null statements. Always document a null body for a for
or while
statement so that it is clear the null body is intentional.
✅ DO use explicit comparisons when testing for failure. Use if (FAIL != f())
rather than if (f())
, even though FAIL may have the value 0 which C considers to be false. An explicit test will help you out later when somebody decides that a failure return should be -1 instead of 0.
Explicit comparison should be used even if the comparison value will never change. e.g. if (!(bufsize % sizeof(int)))
should be written as if (0 == (bufsize % sizeof(int))
to reflect the numeric (not boolean) nature of the test.
A frequent trouble spot is using strcmp
to test for string equality. You should never use a default action. The preferred approach is to use an inline function:
inline bool StringEqual(char *a, char *b) {
return (0 == strcmp(a, b));
}
~ Should ⚠️ YOU SHOULD NOT use embedded assignments. There is a time and a place for embedded assignment statements. In some constructs, there is no better way to accomplish the results without making the code bulkier and less readable.
while (EOF != (c = getchar())) {
/* process the character */
}
However, one should consider the tradeoff between increased speed and decreased maintainability that results when embedded assignments are used in artificial places.
Templates
✅ DO name function templates and class templates the same as one would name non-templates.
✅ DO name template arguments with PascalCase.
Macros
☑️ YOU SHOULD avoid use of macros. It is acceptable to use macros in the following situations. Use outside of these situations should contact the Azure Review Board.
- Platform, compiler, or other environment detection (for example,
_WIN32
or_MSC_VER
). - Emission or suppression of diagnostics.
- Emission or supression of debugging asserts.
- Import declarations. (
__declspec(dllimport)
,__declspec(dllexport)
)
TODO: Need to involve Charlie in how we want to talk about import declarations
✅ DO name macros with ALL_CAPITAL_SNAKE_CASE.
✅ DO prepend macro names with AZ_<SERVICENAME>
to make macros unique.
⛔️ DO NOT use macros where an inline function or function template would achieve the same effect. Macros are not required for code efficiency.
// Bad
##define MAX(a,b) ((a > b) ? a : b)
// Good
template<class T>
[[nodiscard]] inline T Max(T x, T y) {
return x > y ? x : y;
}
⛔️ DO NOT change syntax via macro substitution. It makes the program unintelligible to all but the perpetrator.
Type Safety Recommendations
✅ DO In class types, implement the “rule of zero”, the “rule of 3”, or the “rule of 5”. That is, of the special member functions, a type should implement exactly one of the following:
- No copy constructor, no copy assignment operator, no move constructor, no move assignment operator, or destructor.
- A copy constructor, a copy assignment operator, no move constructor, no move assignment operator, and a destructor.
- A copy constructor, a copy assignment operator, a move constructor, a move assignment operator, and a destructor.
This encourages use of resource managing types like std::unique_ptr (which implements the rule of 5) as a compositional tool in more complex data models that implement the rule of zero.
✅ DO provide types which are usable when default-initialized. (That is, every constructor must initialize all type invariants, not assume members have default values of 0 or similar.)
class TypeWithInvariants {
int m_member;
public:
TypeWithInvariants() noexcept : member(0) {} // Good: initializes all parts of the object
[[nodiscard]] int Next() noexcept {
return m_member++;
}
};
class BadTypeWithInvariants {
int m_member;
public:
BadTypeWithInvariants() {} // Bad: Does not initialize all parts of the object
int Next() {
return m_member++;
}
};
void TheCustomerCode() {
TypeWithInvariants a{}; // value-initializes a TypeWithInvariants, OK
TypeWithInvariants b; // default-initializes a TypeWithInvariants, we want this to be OK
BadTypeWithInvariants c{}; // value-initializes a BadTypeWithInvariants, OK
BadTypeWithInvariants d; // default-initializes a BadTypeWithInvariants, this will trigger
// undefined behavior if anyone calls d.Next()
}
✅ DO define getters and setters for data transfer objects. Expose the members directly to users unless you need to enforce some constraints on the data. For example:
// Good - no restrictions on values
struct ExampleRequest {
int RetryTimeoutMs;
const char* Text;
};
// Bad - no restrictions on parameters and access is not idiomatic
class ExampleRequest {
int m_retryTimeoutMs;
const char* m_text;
public:
[[nodiscard]] int GetRetryTimeoutMs() const noexcept {
return m_retryTimeoutMs;
}
void SetRetryTimeoutMs(int i) noexcept {
m_retryTimeoutMs = i;
}
[[nodiscard]] const char* GetText() const noexcept {
return m_text;
}
void SetText(const char* i) noexcept {
m_text = i;
}
};
// Good - type maintains invariants
class TypeWhichEnforcesDataRequirements {
size_t m_size;
int* m_data;
public:
[[nodiscard]] size_t GetSize() const noexcept {
return m_size;
}
void AddData(int i) noexcept {
m_data\[m_size++\] = i;
}
};
// Also Good
class TypeWhichClamps {
int m_retryTimeout;
public:
[[nodiscard]] int GetRetryTimeout() const noexcept {
return m_retryTimeout;
}
void SetRetryTimeout(int i) noexcept {
if (i < 0) i = 0; // clamp i to the range [0, 1000]
if (i > 1000) i = 1000;
m_retryTimeout = i;
}
};
✅ DO declare variables in structures organized by use in a manner that minimizes memory wastage because of compiler alignment issues and size. All things being equal, use alphabetical ordering.
// Bad
struct Foo {
int A; // the compiler will insert 4 bytes of padding after A to align B
char *B;
int C;
char *D;
};
// Good
struct Foo {
int A; // C is now stored in that padding
int C;
char *B;
char *D;
};
⛔️ DO NOT use enums to model any data sent to the service. Use enums only for types completely internal to the client library. For example, an enum to disable Nagle’s algorithm would be OK, but an enum to ask the service to create a particular entity kind is not.
Const and Reference members
⛔️ DO NOT declare types with const or reference members. Const and reference members artificially make your types non-Regular as they aren’t assignable, and have surprising interactions with C++ Core language rules. For example, many accesses to const or reference members would need to involve use of std::launder
to avoid undefined behavior, but std::launder
was added in C++17, a later version than the SDKs currently target. See C++ Core Working Group CWG1116 “Aliasing of union members”, CWG1776 “Replacement of class objects containing reference members”, and P0137R1 “Replacement of class objects containing reference members” for additional details.
If you want a type to provide effectively const data except assignment, declare all your member functions const. Const member functions only get a const view of the class’ data.
// Bad
class RetrySettings {
const int m_maxRetryCount;
public:
int GetMaxRetryCount() {
// intent: disallow m_maxRetryCount = aDifferentValue;
return m_maxRetryCount;
}
};
// Good
class RetrySettings {
int m_maxRetryCount;
public:
int GetMaxRetryCount() const {
// still prohibits m_maxRetryCount = aDifferentValue; without making RetrySettings un-assignable
return m_maxRetryCount;
}
};
Integer sizes
The following integer rules are listed in rough priority order. Integer size selections are primarily driven by service future compatibility. For example, just because today a service might have a 2 GiB file size limit does not mean that it will have such a limit forever. We believe 64 bit length limits will be sufficient for sizes an individual program works with for the foreseeable future.
✅ DO Represent file sizes with int64_t
, even on 32 bit platforms.
✅ DO Represent memory buffer sizes with size_t
or ptrdiff_t
as appropriate for the environment. Between the two, choose the type likely to need the fewest conversions in application. For example, we would prefer signed ptrdiff_t
in most cases because signed integers behave like programmers expect more consistently, but the SDK will need to transact with malloc
, std::vector
, and/or std::string
which all speak unsigned size_t
.
✅ DO Represent any other integral quantity passed over the wire to a service using int64_t
, even if the service uses a 32 bit constant internally today.
⛔️ DO NOT Use int
under any circumstances, including for
loop indexes. Those should usually use ptrdiff_t
or size_t
under the buffer size rule above.
✔️ YOU MAY Use any integer type in the intN_t
or uintN_t
family as appropriate for quantities not enumerated above, such as calculated values internal to the SDK such as retry counts.
Secure functions
⚠️ YOU SHOULD NOT use Microsoft security enhanced versions of CRT functions to implement APIs that need to be portable across many platforms. Such code is not portable and is not compatible with either the C or C++ Standards. See arguments against.
TODO: Verify with the security team, and what are the alternatives?
Enumerations
✅ DO use enum
or enum class
for values shared “over the wire” with a service, to support future compatibility with the service where additional values are added. Such values should be persisted as strings in client data structures instead.
✔️ YOU MAY provide an ‘extensible enum’ pattern for storing service enumerations which provides reasonable constant values. This pattern stores the value as a string but provides public static member fields with the individual values for customer consumption. For example:
#include <azure/core/strings.hpp> // for Azure::Core::Strings::LocaleInvariantCaseInsensitiveEqual
#include <utility> // for std::move
namespace Azure { namespace Group { namespace Service {
// an "Extensible Enum" type
class KeyType {
std::string m_value;
public:
// Provide `explicit` conversion from string or types convertible to string:
explicit KeyType(const std::string& value) : m_value(value) { }
explicit KeyType(std::string&& value) : m_value(std::move(value)) { }
explicit KeyType(const char* value) : m_value(value) { }
// Provide an equality comparison. If the service treats the enumeration case insensitively,
// use LocaleInvariantCaseInsensitiveEqual to prevent differing locale settings from affecting
// the SDK's behavior:
bool operator==(const KeyType& other) const noexcept {
return Azure::Core::Strings::LocaleInvariantCaseInsensitiveEqual(m_value, other.m_value);
}
bool operator!=(const KeyType& other) const noexcept { return !(*this == other); }
// Provide a "Get" accessor
const std::string& Get() const noexcept { return m_value; }
// Provide your example values as static const members
const static KeyType Ec;
const static KeyType EcHsm;
const static KeyType Rsa;
const static KeyType RsaHsm;
const static KeyType Oct;
};
}}} // namespace Azure::Group::Service
// in a .cpp file:
namespace Azure { namespace Group { namespace Service {
const KeyType KeyType::Ec{"EC"};
const KeyType KeyType::EcHsm{"EC-HSM"};
const KeyType KeyType::Rsa{"RSA"};
const KeyType KeyType::RsaHsm{"RSA-HSM"};
const KeyType KeyType::Oct{"OCT"};
}}} // namespace Azure::Group::Service
Physical Design
TODO: Move this to implementation or move the headers discussion from implementation here
☑️ YOU SHOULD include files using quotes (“) for files within the same git repository, and angle brackets (<>) for external dependencies.
☑️ YOU SHOULD declare all types that are only used within the same .cpp
file in an unnamed namespace. For example:
namespace {
struct HashComputation {
int InternalBookkeeping;
};
} // unnamed namespace
Class Types (including union
s and struct
s)
Throughout this section, class types includes types with class-key struct
or class-key union
, consistent with the C++ Standard.
✅ DO name class types with PascalCase.
✅ DO name public
and protected
member variables with PascalCase.
✅ DO name private
member variables with an m_
prefix, followed by a camelCase name. For example, m_timeoutMs
.
✅ DO name member functions with PascalCase, except where the C++ Standard forbids this. For example, UploadBlob
, or operator[]
.
☑️ YOU SHOULD declare classes with only public members using class-key struct
.
// Good
struct OnlyPublicMembers {
int Member;
};
// Bad
class OnlyPublicMembers {
public:
int Member;
};
☑️ YOU SHOULD define class types without using typedef
s. For example:
// Good: Uses C++ style class declaration:
struct IotClient {
char* ApiVersion;
IotClientCredentials* Credentials;
int RetryTimeout;
};
// Bad: Uses C-style typedef:
typedef struct IotClient {
char* ApiVersion;
IotClientCredentials* Credentials;
int RetryTimeout;
} AzIotClient;
Tooling
We use a common build and test pipeline to provide for automatic distribution of client libraries. To support this, we need common tooling.
✅ DO use CMake v3.7 for your project build system.
Version 3.7 is the minimum version installed on the Azure Pipelines Microsoft hosted agents (https://docs.microsoft.com/azure/devops/pipelines/agents/hosted)
✅ DO include the following standard targets:
build
to build the librarytest
to run the unit test suitedocs
to generate reference documentationall
to run all three targets
Include other targets as they appear useful during the development process.
✅ DO use hidden visibility when building dynamic libraries. For CMake:
set(CMAKE_C_VISIBILITY_PRESET hidden)
set(CMAKE_VISIBILITY_INLINES_HIDDEN ON)
This allows you to use an export macro to export symbols. For example:
#ifndef APPCONF_EXPORT_H
#define APPCONF_EXPORT_H
#ifdef APPCONF_STATIC_DEFINE
# define APPCONF_EXPORT
# define APPCONF_NO_EXPORT
#else
# ifndef APPCONF_EXPORT
# ifdef appconf_EXPORTS
/* We are building this library */
# define APPCONF_EXPORT __declspec(dllexport)
# else
/* We are using this library */
# define APPCONF_EXPORT __declspec(dllimport)
# endif
# endif
# ifndef APPCONF_NO_EXPORT
# define APPCONF_NO_EXPORT
# endif
#endif
#ifndef APPCONF_DEPRECATED
# define APPCONF_DEPRECATED __declspec(deprecated)
#endif
#ifndef APPCONF_DEPRECATED_EXPORT
# define APPCONF_DEPRECATED_EXPORT APPCONF_EXPORT APPCONF_DEPRECATED
#endif
#ifndef APPCONF_DEPRECATED_NO_EXPORT
# define APPCONF_DEPRECATED_NO_EXPORT APPCONF_NO_EXPORT APPCONF_DEPRECATED
#endif
#if 0 /* DEFINE_NO_DEPRECATED */
# ifndef APPCONF_NO_DEPRECATED
# define APPCONF_NO_DEPRECATED
# endif
#endif
#endif /* APPCONF_EXPORT_H */
CMake will automatically generate an appropriate export header:
include(GenerateExportHeader)
generate_export_header(speech
EXPORT_FILE_NAME azure/speech_export.hpp)
✅ DO use clang-format for formatting, with the following command-line options:
cpp-format -style=file -i <file> ...
Using -i
does an in-place edit of the files for style. There is a Visual Studio extension that binds Ctrl-R Ctrl-F to this operation. Visual Studio 2019 includes this functionality by default.
✅ DO generate API documentation with doxygen
.
For example in CMake:
find_package(Doxygen REQUIRED doxygen)
set(DOXYGEN_GENERATE_HTML YES)
set(DOXYGEN_GENERATE_XML YES)
set(DOXYGEN_OPTIMIZE_OUTPUT_FOR_C YES)
set(DOXYGEN_EXTRACT_PACKAGE YES)
set(DOXYGEN_SIMPLE_STRUCTS YES)
set(DOXYGEN_TYPEDEF_HIDES_STRUCT NO)
doxygen_add_docs(doxygen
${PROJECT_SOURCE_DIR}/inc
${PROJECT_SOURCE_DIR}/src
${PROJECT_SOURCE_DIR}/doc
COMMENT "generate docs")
Notice that:
- We use
find_package()
to find doxygen - We use the
DOXYGEN_<PREF>
CMake variables instead of writing your owndoxyfile
. - We set
OPTIMIZE_OUTPUT_FOR_C
in order to get more C appropriate output. - We use
doxygen_add_docs
to add the target, this will generate adoxyfile
for you.
✅ DO provide a CMake option of the form <SDK_NAME>_BUILD_SAMPLES
that includes all samples in the build. For example:
if(AZURE_APPCONF_BUILD_SAMPLES)
add_subdirectory(samples)
endif()
⛔️ DO NOT install samples by default.
Supported platforms
✅ DO support the following platforms and associated compilers when implementing your client library.
Windows
Operating System | Version | Architectures | Compiler Version | Notes |
---|---|---|---|---|
Windows Client | 7 SP1+, 8.1 | x64, x86 | MSVC 14.16.x, MSVC 14.20x | |
Windows 10 Client | Version 1607+ | x64, x86, ARM | MSVC 14.16.x, MSVC 14.20x | |
Windows 10 Client | Version 1909+ | ARM64 | MSVC 14.20x | |
Nano Server | Version 1803+ | x64, ARM32 | MSVC 14.16.x, MSVC 14.20x | |
Windows Server | 2012 R2+ | x64, x86 | MSVC 14.16.x, MSVC 14.20x |
Mac
Operating System | Version | Architectures | Compiler Version | Notes |
---|---|---|---|---|
macOS | 10.13+ | x64 | XCode 9.4.1 |
Linux
Operating System | Version | Architectures | Compiler Version | Notes |
---|---|---|---|---|
Red Hat Enterprise Linux CentOS Oracle Linux |
7+ | x64 | gcc-4.8 | Red Hat lifecycle CentOS lifecycle Oracle Linux lifecycle |
Debian | 9+ | x64 | gcc-6.3 | Debian lifecycle |
Ubuntu | 18.04, 16.04 | x64 | gcc-7.3 | Ubuntu lifecycle |
Linux Mint | 18+ | x64 | gcc-7.3 | Linux Mint lifecycle |
openSUSE | 15+ | x64 | gcc-7.5 | OpenSUSE lifecycle |
SUSE Enterprise Linux (SLES) | 12 SP2+ | x64 | gcc-4.8 | SUSE lifecycle |
☑️ YOU SHOULD support the following additional platforms and associated compilers when implementing your client library.
⚠️ YOU SHOULD NOT use compiler extensions. Examples of extensions to avoid include:
Use the appropriate options for each compiler to prevent the use of such extensions.
✅ DO use compiler flags to identify warnings:
Compiler | Compiler Flags |
---|---|
gcc | -Wall -Wextra |
cpp and XCode | -Wall -Wextra |
MSVC | /W4 |