This document describes a number of improvements to allow C++ projects to be built, tested, published are shared between teams.
Currently, the Gradle C++ plugins can compile and link multiple executables and shared libraries. Dependency management is partially implemented, so that the binaries can be published and resolved in some basic form. There are various missing pieces. For example, dependency meta-data is not published, and resolved binaries have the incorrect names on some platforms.
Given a C++ library or executable, we have:
The project that compiles, links and publishes the binary to be shared. This sharing may happen within the same build or across several different builds via a repository.
A project that uses the published binary in some form. In the case of a published library, the consumer usually installs, links against the library, and runs the result. In the case of a published executable, the consumer generally installs and runs the executable.
A binary that runs on a particular native C runtime. This refers to the physical binary file.
An executable native binary.
A library binary that is linked into an executable or shared library at runtime
A library binary that is linked into an executable or shared library at link time.
A software component that runs on the native C runtime. This refers to the logical entity, rather than the physical.
A native component that represents an application.
A native component that represents a library to be used in other native components.
This story introduces the concept of a static library binary that can be built for a C++ library. This will allow both shared and static variants of a particular library to be built.
- Add
StaticLibraryBinary
type. - Add
LinkStaticLibrary
task type. - Change the
cpp
plugin to:- Add a
StaticLibraryBinary
instance to thebinaries
container for each library added to thelibraries
container. The instance should be called${library.name}StaticLibrary
. - Add a rule that adds a
CppCompile
andLinkStaticLibrary
task for eachStaticLibraryBinary
instance added to thebinaries
container. The link task should be called${binary.name}
.
- Add a
- Change visual C++ toolchain to:
- Use
lib.exe
to assemble the static library.
- Use
- Change the GCC toolchain to:
- Don't use other shared library flags (
-shared
) when compiling source files for a static library. - Use
ar
to assemble the static library.
- Don't use other shared library flags (
- Update the user guide to reflect the fact that static libraries can be built. Include a stand-alone sample that demonstrates how to build a library.
Given:
apply plugin: `cpp-lib`
Running gradle mainStaticLibrary
will build the main static library. Running gradle mainSharedLibrary
will build the main shared library.
Given:
apply plugin: `cpp`
cpp {
sourceSets {
main
}
}
libraries {
custom {
sourceSets << cpp.sourceSets.main
}
}
Running gradle customStaticLibrary customSharedLibrary
will build the static and shared binaries.
- For a build that uses the
cpp-lib
plugin,gradle mainStaticLibrary
will produce the static library. - For a build that uses the
cpp
plugin and defines multiple libraries, each library can be built as both a static and shared library binary. - Can link a static library into an executable and install and run the resulting executable.
This story introduces a lifecycle task for each binary, to allow tasks to be wired into the task graph for a given binary. These tasks will be able to modify the object files or binary files before they are consumed.
- Change
Binary
to extendBuildable
. - Change
NativeComponent
so that it no longer extendsBuildable
. - Change the binaries plugin to add a lifecycle task called
${binary.name}
. - Change the cpp plugin to rename the link tasks for a binary to
link${binary.name}
. - Change
DefaultClassDirectoryBinary
to implementgetBuildDependencies()
(it currently has an empty implementation). - Change
CppPlugin
so that the link task for a binary uses the compile task's output as input, rather than depend on the compile task. - Add an
InstallExecutable
task type and use this to install an executable. - Change
CppPlugin
so that the install task for an executable uses the executable binary as input, rather than depend on the link task. - Add
NativeBinary.tasks
property of typeNativeBinaryTasks
that is aDomainObjectSet<Task>
containing key tasks for a binary.NativeBinaryTasks.link
andNativeBinaryTasks.createStaticLib
are convenience methods for accessing the link/create tasks for a binary.
Note: There is a breaking change here, as the link task for a given binary has been renamed.
apply plugin: 'cpp'
binaries.all { binary ->
def stripTask = task("${binary.name}Strip") {
dependsOn binary.tasks.link
doFirst {
["strip", binary.tasks.link.outputFile].execute()
}
}
binary.builtBy stripTask
}
This story allows some configuration of the settings used to compile and link a given binary.
Some initial support for settings that will be shared by all binaries of a component will also be added.
Later stories will add more flexible and convenient support for customization of the settings for a binary.
- Add the following mutable properties to
NativeBinary
:outputFile
objectFileDirectory
compilerArgs
- Add the following mutable properties to
ExecutableBinary
andSharedLibraryBinary
:linkerArgs
- Add a
binaries
property toNativeComponent
. This is aDomainObjectCollection
of all binaries for the component. - Remove
NativeComponent.compilerArgs
andlinkerArgs
properties. Instead, configuration injection via thebinaries
container can be used to define shared settings for the binaries of a component.
apply plugin: 'cpp-lib'
libraries {
main {
// Defaults for all binaries for this library
binaries.all {
compilerArgs = ['-DSOME_DEFINE']
linkerArgs = ['-lsomelib']
}
// Defaults for all shared library binaries for this library
binaries.withType(SharedLibraryBinary) {
compilerArgs << '/DDLL_EXPORT'
}
}
}
binaries {
mainSharedLibrary {
// Adjust the args for this binary
compileArgs << '-DSOME_OTHER_DEFINE'
linkerArgs << '-lotherlib
}
}
binaries.withType(NativeBinary) {
outputFile = file("$buildDir/${name}/${outputFileName}")
objectFileDirectory = file("$buildDir/${name}/obj")
}
The CI builds include coverage for each supported tool chain. However, the coverage silently ignores tool chains which are not available on the current machine. Instead, the CI builds should asert that every expected tool chain is avilable on the current machine.
Later stories will add further integration test coverage for particular OS and tool chain combinations.
- Change
AbstractBinariesIntegrationSpec
to assert that each expected tool chain is installed on the current machine when runnning as part of a CI coverage build. - Change
AbstractBinariesIntegrationSpec
to use a single tool chain for each machine as part of a CI commit build. For Windows, the test should use a recent version of Visual C++, and for Linux, the test should use GCC. - Install Visual C++ 2010 express, Cygwin and MinGW on the Windows CI agents, as required.
- Install GCC 3 and GCC 4 the linux CI agents, as required.
- Update the server wiki pages to describe the installation steps required for each machine.
This story adds support for C source files as inputs to native binaries.
- Add a
CSourceSet
interface. - Add a
CCompile
task type. - Change the
cpp
plugin to add aCCompile
instance for eachCSourceSet
added to a native binary. - Change the visual C++ toolchain to:
- Not use
/EHsc
when compiling any source. - Use
/TC
to force source language to C for all files in a C source set. - Use
/TP
to force source language to C++ for all files in a C++ source set.
- Not use
- Change the GCC toolchain to:
- Use
gcc
to compile all source files in a C source set. Should also use-x c
to force source language to C. - Use
g++
to compile all source files in a C++ source set. Should also use-x c++
to force source language to C++.
- Use
- Rework the plugins so that there is something like:
- A
cpp-lang
plugin, which adds support for C++ source sets and compiling them to object files. - A
c-lang
plugin, which adds support for C source sets and compiling them to object files - An
assembler-lang
plugin, which adds support for assembler source sets and compiling them to object files. - A
native-binaries
plugin, which adds the base support for native binaries and components. - 'cpp', 'c' and 'assembler' plugins, which apply the language plugin + native binary support
- A
- Change
compilerArgs
,define
andmacros
methods on NativeBinary so that they are language-specific- Replace existing methods with
cppCompiler.args
,cppCompiler.define
andcppCompiler.macros
. - Introduce
cCompiler
equivalents
- Replace existing methods with
- Change the GCC toolchain to use
gcc
to link when there is no C++ source included in a binary.
- Build breaks when C++ source file cannot be compiled.
- Build breaks when C source file cannot be compiled.
- Build breaks when a binary cannot be linked, for each type of binary.
- C compilation is incremental wrt C source files, header files and compiler settings.
- Mixed C/C++ binary, for each type of binary.
- Project has mixed C and C++ header files.
- Verify that C compiler is being used for C, and C++ compiler is being used for C++.
- Manually define C++ and C source sets.
- Can build a binary from C sources with
gcc
wheng++
andas
are not installed
This story adds support for using assembler source files as inputs to a native binary.
- Add an
AssemblerSourceSet
type and allow these to be added to a native binary. - Add an
Assemble
task type. - Change the
cpp
plugin to add anAssemble
instance for each assembler source set added to a native binary. - Change the visual C++ toolchain to:
- Use
ml /nologo /c
to assemble a source file to an object file.
- Use
- Change the GCC toolchain to:
- Use
as
to assemble a source file to an object file.
- Use
- Add
NativeBinary.assembler.args
for providing arguments to the assembler
- Build breaks when source file cannot be assembled.
- Assembly is incremental wrt assembly source files, and assembler settings.
- Mixed C/C++/ASM binary, for each kind of binary
- A project can have all C, C++ source and header files and assembly source files in the same source directory.
- Manually define an assembler source set.
This story reworks the existing C++ source set concepts to reuse the concept of a functional source set from the new JVM language DSL. This will allow a binary to be built from more than one language, and later stories will build on this to add support for specific languages.
- Change
CppSourceSet
to extendLanguageSourceSet
. - Add a mutable
NativeBinary.source
property of typeDomainObjectCollection<LanguageSourceSet>
.- Add a
NativeBinary.source
method that accepts aFunctionalSourceSet
as input, and adds everyLanguageSourceSet
included in that functional source set.
- Add a
- Change the C++ plugin to add a component's source sets to each of the component's binaries.
- Change the C++ plugin to add a
CppCompile
instance for eachCppSourceSet
added to a native binary. - Change the
cpp
plugin to:- Add a
sources.${functionalSourceSet.name}.cpp
C++ source set for every functional source set.
- Add a
- Change the
cpp-exe
andcpp-lib
plugins to:- Add a
sources.main
functional source set - Wire the
sources.main.cpp
source set into themain
executable/library.
- Add a
- Remove
CppExtension
To adjust the by-convention layout:
apply plugin: 'cpp-lib'
sources {
main {
cpp {
source.srcDirs = 'src'
}
}
}
To define custom source sets and components:
apply plugin: `cpp`
sources {
util {
}
app {
cpp.libs << libraries.util
}
windows {
}
}
libraries {
util {
source sources.util
}
app {
source sources.app.cpp
}
}
- Assemble a binary for a component with no source.
- Assemble a binary for a component with multiple source sets.
- Assemble a binary from a functional source set.
- Assemble a binary from a cpp source set.
- Attempt to build a binary from a Java source set or a resource set.
This story adds support for using another library component or binary as input to compile and/or link a given binary. Adding a library component or binary as input implies that the library's header files will be available at compilation time, and the appropriate binaries available at link and runtime.
Some support will be added for expressing shared dependencies at the component level as well at the binary level.
Later stories will build on this to unify the library dependency DSL, so that a consistent approach is used to consume libraries built by the same project, or another project in the same build, or from a binary repository.
- Rename
NativeDependencySet.files
toruntimeFiles
. - Add
NativeDependencySet.linkFiles
. Default implementation should return the runtime files. This will be improved in later stories. - Change
DefaultStaticLibrary
so that the output file is calledlib${library.baseName}.a
when building with GCC on Windows. - Add
LibraryBinary.getAsNativeDependencySet()
. The implementation depends on toolchain and binary type:
Binary type | Toolchain | linkFiles | runtimeFiles |
---|---|---|---|
Shared | Visual C++ | the `.lib` file | the `.dll` file |
Static | Visual C++ | the `.lib` file | empty collection |
Shared | GCC | the `.so` or `.dll` file | the `.so` or `.dll` file |
Static | GCC | the `.a` file | empty collection |
- The
includeRoots
property of theNativeDependencySet
implementation for a binary should return the set of header directories exported by the library. - Add
NativeBinary.libraries
property of typeDomainObjectCollection<NativeDependencySet>
. - Change the C++ plugin to add the dependencies of each C++ source set to this collection.
- Change the C++ plugin to wire the inputs of the binary's compile and link tasks based on the contents of the
NativeBinary.libraries
collection. - Add
Library.getShared()
andgetStatic()
methods which returnNativeDependencySet
imlementations for the shared and static variants of the library. - Add
NativeBinary.lib(Object)
method which accepts anything that can be converted to aNativeDependencySet
:- A
NativeBinary
is converted by calling itsgetAsNativeDependencySet()
method. - A
NativeDependencySet
can be used as is. - A
Library
is converted by calling itsgetShared().getAsNativeDependencySet()
method.
- A
- Add
CppSourceSet.lib(Object)
method which accepts the above types.
Note that this story does not include support for including the transitive dependencies of a library at link time and runtime.
binaries {
mainExecutable {
// Include the static variant of the given library in the executable
lib libraries.main.static
// Include the default variant of the given library in the executable
lib libraries.utils
// Include the given binary in the executable
lib binaries.someSharedLibrary
}
}
cpp {
sourceSets {
main {
lib libraries.main
lib libraries.util.shared
lib binaries.someSharedLibrary
}
}
}
- Install and run an executable that:
- Uses a mix of static and shared libraries.
- Uses a mix of libraries from the same project, same build and from different builds.
- Use a (static, shared) library that depends on another (static, shared) library.
- In each case, verify that only shared libraries are included in the install image.
- In each case, remove the original binaries before running the install image.
- A dependency on a binary overrides a dependency on the library that produced the binary.
- Need a new name for
NativeDependencySet
. - Need some way to convert a
NativeDependencySet
to a read-only library. - Need to apply a lifecycle to the resolved libs of
CppSourceSet
andNativeBinary
. - Improve consumption of libraries from other projects.
- Some mechanism to use the static binary as default for a library.
- Some mechanism to select static or dynamic linkage for each dependency of a binary.
- Infer incoming libraries from component dependencies.
- Samples that demonstrates how to link a static library into an executable.
- Move Component and Binary interfaces to new packages.
- Add some way to create ad hoc
NativeDependencySet
instances, for example, a library produced by another build tool. - Need to be able to fine-tune compile time, link time and runtime inputs.
- Merge
CppSourceSet.lib()
andCppSourceSet.dependency()
. - Allow a
Binary
to be attached to a publication. - Update publication so that a binary's include, link and runtime files are published.
- Need to be able to deal with the fact that only position-independent binaries can be linked into position-independent binaries
- Make it possible to build a position-independent variant of a static library binary
- Add the '-fPIC' flag when compiling to ensure that the static library can be included in a shared library
- Change dependency resolution to choose the position-indepenent variant of a static library when linking into a shared library
- Come up with consistent naming scheme for language plugins: 'cpp', 'c', 'assembler', 'java-lang', 'scala-lang', etc
- Declare a dependency on another source set.
- Need a 'cross-platform' and 'platform-specific' source set.
- Add compile dependencies to each source set.
- Add link dependencies to each source set, use these to infer the link dependencies of the binary.
- Change
NativeDependencySet
to handle separate C and C++ headers. - Replace
SourceDirectorySet
with something that is actually a set of source directories.- Use this for sources and headers
- Can access as a set of directories, a set of files or a file tree
In the majority of cases, it makes sense to have a single named functional source set for each component, with the source set name and source directory names matching the component name. This story ensures that such a source set are created automatically for each component.
- Add a rule to the 'native-binaries' plugin that for each native component:
- Creates a functional source set named with the component name, if such a source set does not yet exist.
- Configures the functional source set to be a source input for the component.
- Remove explicit configuration and wiring of functional source sets for each component from tests and samples.
- Programmatically create the functional source set prior to adding the matching component.
This story adds initial support for building multiple variants of a native component. For each variant of a component, a binary of the appropriate type is defined.
- Add a
flavors
container toNativeComponent
- If no flavors are defined, then implicitly define a
default
flavor. - Variants will be built for all available flavors:
- With a single flavor, the binary task names and output directories will NOT contain the flavor name.
- With multiple flavors, task names and output directories for each variant will include the flavor name.
- Add a property to
NativeBinary
to allow navigation to the flavor for this binary. - When resolving the dependencies of a binary
b
, for a dependency on libraryl
:- If
l
has multiple flavors defined, select the binary with the same flavor asb
. Fail if no binary with matching flavor. Match flavors based on their name. - If
l
has a single flavor (default or defined), select the binary with that flavor.
- If
libraries {
main {
flavors {
main {}
withOptionalFeature {}
}
binaries.matching { flavor == flavors.main } {
cppCompiler.args '-DSOME_FEATURE
}
}
}
This will define 4 binaries:
- library: 'main', flavor: 'main', packaging: 'static'
- library: 'main', flavor: 'main', packaging: 'shared'
- library: 'main', flavor: 'withOptionalFeature', packaging: 'static'
- library: 'main', flavor: 'withOptionalFeature', packaging: 'shared'
- Executable with flavors depends on a library with a single flavor which depends on a library with flavors.
- Make it easy to have the same set of variants for all components.
- Add a 'development' assemble task, which chooses a single binary for each component.
- Need to make standard 'build', 'check' lifecycle tasks available too.
- Formalise the concept of a naming scheme for binary names, tasks and file paths.
- Add a convention to give each binary a separate output directory (as the name of each variant binary can be the same).
- Add a convention to give each binary a separate object file directory.
- Need to be able to build a single variant or all variants.
- Need to consume locally and between projects and between builds.
- Need a hook to infer the default variant.
- Need to handle dependencies.
- Need to publish all variants.
This story adds support for building a native component using multiple tool chains. Each variant will have a tool chain associated with it.
- Build author can define a set of tool chain that may be used to build a component.
- If no tool chain is defined, then a single default tool chain will be used.
- From the set of defined tool chains, a set of available tool chains will be determined for building.
- Variants will be built for all available tool chains.
- With a single defined tool chain, the binary task names and output directories will NOT contain the tool chain name.
- With multiple defined tool chains, task names and output directories for each variant will include the tool chain name.
- When resolving the dependencies of a binary
b
, for a dependency on libraryl
:- If
l
has multiple toolchains defined, select the binary with the same toolchain asb
. Fail if no binary with matching toolchain. Match toolchains based on their name. - If
l
has a single toolchain, select the binary with that toolchain. - In both instances, assert that the toolchain for the selected binary is ABI compatible with the toolchain being used.
- If
- When building component, will attempt to build variants for all available tool chains.
// Global set of defined tool chains. A future story may allow this to be tweaked per-component
toolChains {
gcc(Gcc) {} // binaries with default names, found on Path
gcc3(Gcc) {
path "/opt/gcc/3.4.6/bin" // binaries with default names, found in supplied path
}
mingw(Gcc) {
path "C:/MinGW/bin"
}
gccCrossCompiler(Gcc) {
path "/opt/gcc/4.0/bin" // Needed to update the path
// Custom binary file paths and arguments
cCompiler.executable "gccCustom_gcc"
cppCompiler.executable project.file("/usr/bin/gccCustom_g++")
assembler.executable "/usr/bin/gccCustom_as"
linker.executable "gcc"
staticLibArchiver.executable "ar"
}
// Path already setup and binaries available
visualCpp(VisualCpp) {}
// Setup paths based on installation directory
visualCpp(VisualCpp) {
installDir "D:/Programs/Microsoft Visual Studio 10.0"
assembler.executable "ml64.exe"
}
}
- With no toolchain defined, build on windows with Visual C++/MinGW/cygwin-gcc in path and on linux with gcc4/gcc3 in path. (existing tests)
- Build with gcc3 & gcc4 on Linux in a single invocation, with neither tool chain in path.
- Build with Visual C++, MinGW and GCC on Windows in a single invocation, with no tool chain in path.
- Reasonable error message when no tool chain is defined and default is not available.
- Reasonable error message any defined tool chain not available.
- Build an executable with multiple toolchains that uses a library with a single toolchain that uses a library with multiple toolchains.
- Reasonable behaviour when no tool chains are available on the current machine.
- Need separate compiler, linker and assembler options for each toolchain.
- Need shared compiler, linker and assembler options for all toolchains.
- Need to consume locally and between projects and between builds.
- Need to be able to build for a single toolchain only.
- Easy way to detect (and use) the set of available tool chains.
- Define a tool chain as a type and required version (eg GCC v4.+)
- Attempt to locate the tool chain in path (if no
path
provided) - Verify the version for defined tool chain (if
path
is provided)
- Attempt to locate the tool chain in path (if no
- VisualCppToolChain should automatically switch between different executables for different target architectures.
This story adds support for building a component for multiple architectures. Introduce the concept of a native platform, which may have an associated architecture and operating system. Each ToolChain can be asked if it can build for a particular platform. Each variant has a platform associated with it.
- Native binaries plugin adds new types:
Architecture
,Platform
andPlatformContainer
.- Add an extension to
project
of typePlatformContainer
namedtargetPlatforms
.
- Add an extension to
- Add
architecture
property toPlatform
- If a platform has no architecture defined, then the architecture of the build machine is used.
- If no target platform is defined and added to the PlatformContainer, then the a single default platform is added.
- Add a
platform
property toNativeBinary
to allow navigation from the binary to its corresponding platform. - Split
PlatformToolChain
out ofToolChainInternal
, withToolChainInternal.target(Platform) -> PlatformToolChain
PlatformToolChain
may contain built-in knowledge of arguments required to build for target platform.- Additionally, platform-specific arguments may be supplied in the build script
- For each tool chain, a variant will be built for each defined platform. Later stories will allow a tool chain to only target certain platforms.
- With a single defined platform, the binary task names and output directories will NOT contain the platform name.
- With multiple defined platforms, task names and output directories for each variant will include the platform name.
- When resolving the dependencies of a binary
b
, for a dependency on libraryl
:- If
l
has multiple platforms defined, select the binary with the same platform asb
. Fail if no binary with matching platform. Match platforms based on their name. - If
l
has a single platform, select the binary with that platform. - In both instances, assert that the platform for the selected binary is compatible with the platform the binary is being built for
- If
- Change the GCC and Visual C++ toolchain adapters to invoke the assembler, compiler, linker and static lib bundler with the appropriate architecture flags.
- Update the
c-with-assembler
sample to remove the command-line args and to use the DSL to select the appropriate source sets.
// Project definition of target platforms.
// Later stories may allow this to be separately configured per component.
targetPlatforms {
x86_standard {
architecture "x86" // Synonym for x86-32, IA-32
}
x86_optimised {
architecture "x86" // Synonym for x86-32, IA-32
}
x64 {
architecture "x64" // Synonym for x86-64, AMD64
}
itanium {
architecture "IA-64"
}
}
binaries.all {
if (binary.platform == platforms.x86_optimised) {
// Customize arguments for "x86_optimised" variants
}
if (binary.platform.architecture.name == "x86") {
// Customize arguments for all "x86_standard" and "x86_optimised" variants
}
}
- For each supported toolchain, build a 32-bit binary on a 64-bit machine.
- Verify that the correct output was produced
- On linux run
readelf
over the object files and binaries - On OS X run
otool -hv
over the object files and binaries - On Windows run
dumpbin
over the object files and binaries
- On linux run
- Build an executable with multiple architectures that uses a library with a single architecture that uses a library with multiple architectures.
- Need to be able to build for a single architecture or all available architectures.
- Need to discover which architectures a tool chain can build for.
- Need to handle universal binaries.
- Need separate compiler, linker and assembler options for each platform.
- Infer the default platform and architecture.
- Define some conventions for architecture names.
- Intel 32bit:
ia-32
,i386
,x86
- Intel 64bit:
x86-64
,x64
(Windows),amb64
(BSD and Debian linux distributions, Solaris),x86_64
(linux kernel, OS X, Fedora, GCC tools) - Intel Itanium:
ia-64
- PowerPC 32bit:
ppc
- PowerPC 64bit:
ppc64
- Sparc: ??
- ARM: ??
- Intel 32bit:
This story adds support for cross-compilation. Add the concept of an operating system to the platform.
- Add
OperatingSystem
and simple type extending Named - Add
Platform.operatingSystem
property. Should default to the operating system of the build machine. - Add
ToolChain.targetPlatform
add to the set of supported target platforms for a tool chain.
toolChains {
gccXCompile(GccToolChain) {
path "/my/crosscompiler"
targetPlatform "linux_x86" // target by name
targetPlatform {
it.operatingSystem.name == "windows" && it.architecture.name == "x86"
}
}
}
targetPlatforms {
linux_x86 {
operatingSystem "linux"
architecture "x86" // Synonym for x86-32, IA-32
}
linux_x64 {
operatingSystem "linux"
architecture "x64" // Synonym for x86-64, AMD64
}
osx_64 {
operatingSystem "osx"
architecture "x64"
}
itanium {
architecture "IA-64"
}
}
- Different source files by platform
- Need to be able to build for a single platform or all available platforms.
- Need separate compiler, linker and assembler options for each operating system.
- Need to discover which platforms a tool chain can build for.
- Define some conventions for operating system names.
- Add some opt-in way to define variants using a matrix of (flavour, tool chain, architecture, operating system).
TBD
This story add support for using a library which has dependencies on other libraries.
Given a library a
that uses another library b
as input:
-
When compiling a binary that uses library
a
, then include the exported headers for librarya
but not the headers for libraryb
. -
The link-time files of a shared library
a
, are the link binaries for shared librarya
only (the.so
or the.lib
file). -
The link-time files of a static library
a
, are the link binaries for static librarya
(the.a
or.lib
file) and the link-time files of its direct dependencies. -
The runtime files of a shared library
a
, are the runtime binaries for shared librarya
(the.so
or the.dll
file) and the runtime files of its direct dependencies. -
The runtime files of a static library
a
, are the runtime files of its direct dependencies. -
Change the native model to use deferred configuration:
- Configure a source set completely before using it as input to a component or binary.
- Configure a component completely before using it as a source set dependency or defining its binaries.
- Configure a binary completely before defining tasks for it.
- Configure a component completely before defining tasks for it.
- Need to apply conflict resolution as we can't include the static and shared binaries for a given library at link time.
- Add a
DomainObjectCollection
sub-interface with methodall(map, action)
. This method is equivalent to callingmatching(predicate, action)
, wherepredicate
is a spec that selects all objects whose property values equal those specified in the map. This method can be used to do delayed configuration of the elements of the collection. - Change
NativeComponent.binaries
andBinariesContainer
to provide implementations of this interface.
apply plugin: 'cpp-lib'
libraries {
main {
// Defaults for all binaries for this library
binaries.all {
…
}
// Defaults for all shared library binaries for this library
binaries.all(type: SharedLibraryBinary) {
…
}
// Defaults for all binaries built by Visual C++
binaries.all(toolchain: toolchains.visualCpp) {
…
}
// Defaults for all shared library binaries built by Visual C++
binaries.all(toolchain: toolchains.visualCpp, type: SharedLibraryBinary) {
…
}
}
}
binaries.all(name: 'mainSharedLibrary') {
…
}
binaries.all(type: NativeBinary) {
…
}
- Roll out the predicate methods to other containers.
- Need to deal with things like 'all versions of visual C++', 'all toolchains except visual C++', 'visual C++ 9 and later', 'gcc 3' etc.
- Need to deal with things like 'all unix environments', 'all windows environments', etc.
- Nice to have a way to select all binaries that have linker settings (ExecutableBinary + SharedLibraryBinary).
Story: Allow easy creation of all variants, all possible variants and a single variant for each component
- Add an
assemble
lifecycle task for each component. - Running
gradle assemble
should build all binaries for the main executable or library - Running
gradle uploadArchives
should build and publish all binaries for the main executable or library - Add a task for creating a single 'developerImage' for each component.
- Build
debug
variant where possible - Build shared library variants
- Build for the current Operating System and architecture
- Build
This story defers creation of the binaries for a native component until after the component has been fully configured. This will be used in later stories to allow different variants to be defined for a component.
- Add an internal (for now)
ConfigurationActionContainer
interface, with a singleonConfigure(Runnable)
method. - Add a project scoped implementation of this interface. The
onConfigure()
method is invoked just before theafterEvaluated
event. - Change the publishing plugin to register an action that configures the publications of the project.
- Change the handling of
@DeferredConfigurable
so that a@DeferredConfigurable
is not automatically configured during project configuration. Instead, it is configured on demand. - Change the C++ plugin to register an action that configures the components of the project and then creates the binaries of each component.
Note: this story introduces some breaking changes, as the binaries and their tasks will not be defined when the build script is executed. Later stories will improve this.
apply plugin: 'cpp-lib'
libraries {
main {
// Defaults for all binaries for this library
binaries.all {
…
}
// Defaults for all shared library binaries for this library
binaries.withType(type: SharedLibraryBinary) {
…
}
// Note: These will not work as expected
binaries.each { … }
binaries.find { … }
binaries.size()
}
}
binaries.all {
…
}
binaries.withType(NativeBinary) {
…
}
// Note: this will not work as previously. It will fail because the binary does not exist yet
binaries {
mainSharedLibrary { … }
}
//Note: this will not work as previously. It will fail because the task does not exist yet
compileMainSharedLibrary { … }
- Introduce a
DomainObjectCollection
that uses delayed configuration of its elements. - Introduce a
NamedDomainObjectCollection
that uses delayed configuration of its elements. - Warn when using binaries container in a way that won't work.
- Warn when mutating component after binaries have been created.
This story defers configuration of the tasks for a binary or component until after the object has been fully configured.
- Change the C++ plugin to create the tasks for each native binary after the binary has been configured.
- Change the C++ plugin to create the tasks for each component after the tasks for the binary have been configured.
- Remove the convention mappings from the C++ plugin.
- Allow navigation from a
Binary
to the tasks associated with the binary. - Some way to configure the tasks for a binary and publication.
There are two approaches to extracting the dependency information from source files: parse the source files looking for #include
directives, or
use the toolchain's preprocessor.
For Visual studio, can run with /P
and parse the resulting .i
file to extract #line nnn "filename"
directives. In practise, this is pretty slow.
For example, a simple source file that includes Windows.h
generates 2.7Mb in text output.
For GCC, can run with -M
or -MM
and parse the resulting make file to extract the header dependencies.
The implementation will also remove stale object files.
- Change a header file that is included by one source file, only that source file is recompiled.
- Change a header file that is not included by any source files, no compilation or linking should happen.
- Change a header file that is included by another header file.
- Change a compiler setting, all source files are recompiled.
- Remove a source file, the corresponding object file is removed.
- Rename a source file, the corresponding object file is removed.
- Remove all source files, all object files and binary files are removed.
- Allow native binary instances to be added manually.
- Change
NativeBinary
so that not every binary has an associated component.
- Can define a standalone executable, shared library or static library binary, and the appropriate tasks are created and configured appropriately.
- Can define and configure standalone compile and link tasks.
- Add a hook to allow the generated compiler and linker command-line options to be tweaked before forking.
- Add properties to set macros and include directories for a binary, allow these to be set as toolchain specific compiler args (ie -D and -I) as well.
- Add properties to set system libs and lib directories for a binary, allow these to be set as toolchain specific linker args (ie -l and -L) as well.
- Add set methods for each of these properties.
- Split the compiler and linker settings out to separate types.
Resource files can be linked into a binary.
- Resource files are compiled using
rc
to a.res
file, which can then be linked into the binary. - Add a
resource
source set type and allow these to be attached to a binary. - Add the appropriate tasks to compile resource scripts.
- A
.def
file lists__stdcall
functions to export from a DLL. Can also use__declspec(dllexport)
in source to export a function. - Functions are imported from a DLL using
__declspec(dllimport)
.
- Use
midl
to generate server, client and header source files.
- Need to deal with source sets that are generated.
- Need a
CppSourceSet.getBuildDependencies()
implementation.
- When linking a native binary, link against exactly the same version of each library that we compiled against, plus any additional link-time dependencies (resources, for example).
- When installing a native executable, also install exactly the same versions of each library that we linked against, plus any additional runtime dependencies.
Generally, C++ test frameworks compile and link a test launcher executable, which is then run to execute the tests.
To implement this:
- Define a test source set and associated tasks to compile, link and run the test executable.
- It should be possible to run the tests for all architectures supported by the current machine.
- Generate the test launcher source and compile it into the executable.
- It would be nice to generate a test launcher that is integrated with Gradle's test eventing.
- It would be nice to generate a test launcher that runs test cases detected from the test source (as we do for JUnit and TestNG tests).
- Need a
unitTest
lifecycle task, plus a test execution task for each variant of the unit tests. - Unit test executable needs to link with the object files that would be linked into the main executable.
- Need to exclude the
main
method.
TBD
- Allow the visual studio project file to be generated.
- Merge with existing project file, as for IDEA and Eclipse.
- Add hooks for customizing the generated XML.
A producer project produces a single shared library, for a single platform. The library depends on zero or more other dynamic libraries. The library is published to a repository.
A consumer project resolves the library from the repository, and links an executable against it. Some libraries may be installed on the consumer machine, and the remaining libraries are available in a repository.
Out of scope is the publishing of the resulting executable (this is a later story, below).
On some platforms, such as UNIX platforms, linking is done against the shared library binary. On Windows, linking is done
against the library's export file (.lib
), which is created when the library is linked.
On most platforms, the name of the binary file is important, and at runtime must match the name that was used at link time. On UNIX platforms, the so_name (or install_path on OS X) that is built into the library binary is used. If not present, the absolute path of the library binary file is used. This means that in order to execute against a shared library.
Generally, this means that the library must be installed in some form before it can be linked against.
To implement this story:
- Producer project publishes the dependency meta-data for the library.
- Producer project published the library's export file (.lib) when building on Windows.
- Consumer project uses correct names for resolved libraries.
- Consumer project sets the UNIX file executable permission for resolved executables on UNIX filesystems.
- Separate out compile, link and runtime dependencies, so that different artifacts can be used at compile, link and runtime.
- Consumer project installs the libraries into a location where the executable can find them, with their correct names, and uses these locations at link time.
- Consumer determine which libraries are already installed on the machine, and uses these from their installed location at link time.
- Define some standard artifact types for shared libraries, header archives and export files.
A producer project produces a single executable, for a single platform. The executable depends on zero or more shared libraries. The executable is published to a repository.
A consumer project resolves the executable from the repository, and executes it. Some libraries may be installed on the consumer machine, and the remaining libraries are available in a repository.
On most platforms, executables must follow a certain platform-specific convention. On UNIX platforms, for example, the executable must have the execute
permission set. On Windows platforms, the executable should have a .exe
extension.
To implement this:
- There are a number of tasks in common with publishing and resolving shared libraries above.
- Producer project publishes the dependency meta-data for the executable.
- Consumer project uses correct names for resolved executables.
- Consumer project sets the UNIX file executable permission for resolved executables on UNIX filesystems.
- Consumer project installs the executable and libraries into a location where they are executable.
- Define some standard artifact types for executables.
A producer project compiles and links a shared library for multiple Intel x86 architectures, for a single operating system. The library has zero or more dependencies. The library is published to a repository.
A consumer project links and runs an executable against the appropriate variant of the library, resolved from the repository.
Out of scope for this work is support for other chipsets, or projects that build a library for multiple chipsets.
There are 2 main parts to the architecture that we are interested in: The CPU instruction set that is being used, and the data model. Usually, but not always, a 32-bit processor instruction set is used with 32-bit data model, and a 64-bit processor instruction set is used with 64-bit data model.
Usually, it is possible to combine different instruction sets in the same binary. So, when linking a binary targetting the amd64 CPU, it is fine to link against a library built for the x86 CPU. It is not possible to combine different data models in the same executable.
On OS X, a binary may target multiple architectures, as a universal binary. It should be noted that a universal binary simply supports more than one architectures, but not necessarily every architecture as the name suggests. For example, a universal binary might include x86 & amd64 suport but no ppc or ppc64 support.
File names are important here. Generally, architecture is not encoded in the binary file name. The build must be able to distinguish between binaries that have the same file name (and install path) but are built for different architectures.
It is not always possible to build the binaries for more than one architecture on a given machine. On Linux machines, the system libraries for the target architecture must be installed and available for linking. On Windows machines, a separate compiler must be used for each architecture.
To implement this:
- Add appropriate tasks so that producer project can compile, link and publish the binaries for all available architectures in a single build invocation.
- Add some mechanism for the developer to select the architectures they are interested in from the command-line and tooling API.
- Include in the published meta-data information about which (CPU + data model) each binary was built for.
- Consumer project selects the binaries with the appropriate (CPU + data model) when resolving the link and runtime dependencies for the executable.
- Allow compiler and linker settings to be specified for each architecture.
- Allow resolved binaries to have the same file name across architectures. For example, a shared library should be called
libsomething.so
regardless of whether it happens to be built for the x86 or amd64 architectures. - Define some standard names for CPU instruction sets and architectures, plus mappings to platform specific names.
- Define some default architectures for the x86 chipset. So, every C++ binary may be built for the x86 and amd64 architectures by default.
To generate the binaries:
- For windows, run the 'x86' or 'x86_amd64' compiler.
- For linux + intel CPU, run with gcc with -m32 or -m64.
- For OS X, run gcc with -arch i386, -arch x86_64, -arch ppc, -arch ppc64. Can include multiple times to generate a universal binary. Need to fix GRADLE-2431.
Native architecture names:
- OS X: i386, x86_64, ppc, ppc64
A producer project compiles, links and publishes a shared library for multiple combinations of operating system and architecture. The library depends on zero or more shared libraries. The library is published to a repository.
A consumer project compiles, links and runs an executable against the libary.
Out of scope is cross compilation for other platforms, or building binaries for multiple versions of the same operating system.
Generally, a given machine will not be able to build the binaries for all target operating systems. This means that multiple coordinated builds will be involved.
For the purposes of this story, this coordination will be manual. A human or CI server will trigger a number of builds, each of which builds a subset of the binaries and uploads them. Finally, a build that uploads the meta-data and other operating system independent binaries (header, jars, source and so on) will be triggered. See this story for a description of how this will be improved.
This multi-build approach will also be used to allow binaries for multiple chipsets, and for multiple operating system versions, to be built. There are currently no stories for these use cases.
There are two aspects of operating system that we are interested in: the operating system + version. The version is really a proxy for other attributes we don't model yet: system calling convention, ABI version, language runtime version, which system APIs are available, and so on.
File names are important here. Often, the platform is not encoded int the file name. The build must handle binaries that have the same name (and install path) but are built for different operating systems.
To implement this:
- Add appropriate tasks so that the producer project can compile, link and publish the binaries for the current machine's operating system.
- Allow the binaries for a given version of the library to be built and published from multiple machines.
- Include in the published meta-data information about which operating system + version each binary was built for.
- Consumer project selects the binaries for the appropriate operating system when resolving link and runtime dependencies for the executable.
- Allow compiler and linker settings to be specified for each operating system.
- Allow dependencies to be declared for each operating system.
- Allow the producer to declare a dependency on operating system version (or range, more likely).
- Allow resolved binaries to have the same file name across operating system.
- Define some standard operating system names.
Producer project publishes a library consisting of header files only (e.g. a library of C++ template classes).
Consumer project compiles an executable against this library.
To implement this:
- Allow zero or more binaries for a given library at link and runtime. Also allow for zero or more header files.
Producer project publishes a shared library with both debug and release variants.
Consumer project compiles, links and debugs an executable against this library.
Implementation-wise, this problem is similar in some respects to handling multiple architectures.
Usually, it is fine to link a release build of a libary into a debug binary, if the debug variant is not available. It is not fine to link a debug build of a library into a release binary. On Windows, it is not ok to link a release build of a library into a debug binary.
On some platforms, additional artifacts are required to debug a binary. On gcc based platforms, the debug information is linked into the binary.
On Windows, the debug information is contained in a separate program database file (.pdb
).
On all platforms, the source files for the binary are required.
To implement this:
- Producer project publishes the program database file (.pdb) for Windows binaries.
- Producer project publishes the source files for the library.
- Include in the published meta-data, information about whether the binary is a release or debug binary.
- Separate out debug time dependencies from runtime.
- Consumer project selects the approprate release or debug library binraries when resolving link, execute and debug dependencies.
- Consumer project installs the pdb and source files into the appropriate locations, so the debugger can find them.
- Allow compiler and linker settings to be specified separately for release and debug binaries.
- Define a set of default build types (debug and release, say).
- Allow the producer and consumer projects to define custom build types.
To generate the release binaries (default):
- Enable conservative optimisations.
- Compile with -DNDEBUG
- Compile with -g0
To generate the debug binaries (default):
- Disable optimisation.
- Compile with -g or /Zi
- Link with /DEBUG
Publisher project publishes a shared library.
Consumer project compiles, links and publishes a shared library that includes the shared library.
To implement this:
- Add tasks to the publisher project to allow both static and shared library binaries to be built and published.
- Include in the published meta-data, information about which static libraries are linked statically into the binary.
- Consumer project selects either the static or dynamic binaries for a given dependency at link time. A dependency that is statically linked into a binary has no files that are required at runtime, but may have files that are required at debug time.
TBD
TBD
- General purpose tree artifact.
- Handling for system libraries.
- Building for multiple chipsets.
- Selecting a compatible architecture at resolve time. For example, if I'm building for the amd64 cpu, I can use an x86 cpu + 64 bit data model.
- Selecting a compatible operating system version at resolve time.
- Selecting a compatible release binary when the debug binaries are not available.
- Need to include meta-data about which runtime an DLL is linked against. Same for UNIX shared libraries, but less common.
- Need to include meta-data about which optimisation level a binary is compiled for.
- Cross compilation.
- Custom variants.
- Calling convention.
- Can in theory share the compile task between a static library and an executable built from the same source.
- Publishing and resolving RPM/DEB/NuGet/pkg-config/ etc.
- Support for profiling builds: build with profiling enabled, execute a bunch of times, then build again using the profiling information.
- Support for install distributions, which may bundle some, all, or none of the runtime dependencies (including language runtime libraries).
- Understand the various output file types: PE, ELF, Mach-O, COFF
- Bare-bones tool chain using GNU binutils
- Should be able to run the C preprocessor on assembler source file.