working on c and cmake plugins

This commit is contained in:
Jörg Prante 2024-02-11 14:28:27 +01:00
parent 7ea6ca3d65
commit d616184a2e
27 changed files with 1543 additions and 1 deletions

201
gradle-plugin-c/LICENSE.txt Normal file
View file

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View file

@ -0,0 +1,8 @@
This work is derived from
https://github.com/jjohannes/plain-c
as of January 14, 2024
(Apache License 2.0)

81
gradle-plugin-c/README.md Normal file
View file

@ -0,0 +1,81 @@
# C - Gradle plugin
An experimental plugin for customized **C** development with Gradle.
It showcases how to use tasks that are provided by Gradle core for **C** development,
such as `org.gradle.language.c.tasks.CCompile`, directly as part of a custom build system.
This can make sense in a highly customized Gradle build system where specific (and multiple) **C** compilation/assembling/linking
steps are part of a larger build pipeline with other custom Gradle tasks.
The plugin provides an extension called `c` to:
- Set the `targetPlatform` property of a task to a generic platform via `targetPlatform.set(c.platform())`
- Set the `toolChain` property of a task to a locally installed tool via `toolChain.set(c.localTool("version", "/path/to/tool", ".o"))`
- Set the `toolChain` property of a task to a tool located in a ZIP file in a Maven repository via `toolChain.set(c.repositoryTool("group", "name", "version", "path/in/zip", ".o"))`
## Example
```
plugins {
id 'org.xbib.gradle.plugin.c"
}
tasks.register<CCompile>("compileC") {
toolChain.set(c.localTool("14.0.0", "/usr/bin/clang", ".o"))
targetPlatform.set(c.platform())
source(layout.projectDirectory.files("src/c").asFileTree)
includes(layout.projectDirectory.files("src/headers"))
compilerArgs.add("-S")
objectFileDir.set(layout.buildDirectory.dir("out/o"))
}
val compileC2 = tasks.register<CCompile>("compileC2") {
toolChain.set(c.repositoryTool("com.example", "vendor-x", "1.2", "bin/xcc", ".src"))
targetPlatform.set(c.platform())
source(layout.projectDirectory.files("src/c-special").asFileTree)
includes(layout.projectDirectory.files("src/headers"))
objectFileDir.set(layout.buildDirectory.dir("out/src"))
}
tasks.register<Assemble>("assembleC2") {
toolChain.set(c.repositoryTool("com.example", "vendor-x", "1.2", "bin/xas", ".o"))
targetPlatform.set(c.platform())
source(compileC2.map { it.objectFileDir.asFileTree })
includes(layout.projectDirectory.files("src/headers"))
assemblerArgs = listOf("-D")
objectFileDir = layout.buildDirectory.dir("out/o2").get().asFile
}
```
## How does this work?
In order to fully configure one of the native tasks of Gradle (such as `org.gradle.language.c.tasks.CCompile`),
you require a **NativeToolchain** (instance of `org.gradle.nativeplatform.toolchain.NativeToolChain`)
and a **TargetPlatform** (instance of `org.gradle.nativeplatform.platform.NativePlatform`).
Gradle's own [native languages plugins](https://docs.gradle.org/current/userguide/plugin_reference.html#native_languages)
pre-configure these task properties through the so-called _native toolchain registry_.
This allows, for example, to compile the same code for multiple target platforms with different tools in the same build.
This is great, if this is what you need. However, in a scenario where we do not care about this feature and would rather register
tasks directly, we miss flexibility as the _native toolchain registry_ in Gradle is not very flexible right now and makes it difficult or impossible to register
your own tools (and it has not been improved for several years).
The [CExtension.java](src/main/java/org/xbib/gradle/plugin/c/CExtension.java) class now allows you to
set the `targetPlatform` and `toolChain` properties of any task without the _native toolchain registry_.
It does so by utilising internal Gradle APIs to:
- Construct a generic `NativePlatform` object. This is the same everywhere and will be ignored during tool selection.
In our scenario, we do not care about different platforms.
- Construct a `NativeToolChain` instance based on a concrete executable. Either by providing a concrete path to
an executable (`localTool(...)`) or coordinates pointing at a ZIP file in a Maven repository (`repositoryTool(...)`).
In the latter case, Gradle's dependency management is used to find, download, extract and cache the tool.
Behind the custom `NativeToolChain` implementation is a custom `PlatformToolProvider` implementation and extensions of
Gradle's `NativeCompiler` implementations. This allows for more customization and extension of Gradle's functionality
in this area. This uses a lot of APIs from `internal` packages. Which, however, have not been touched for years.
This showcase could help to determine which APIs in this area should probably be public.

View file

@ -0,0 +1,30 @@
plugins {
alias(libs.plugins.publish)
}
apply plugin: 'com.gradle.plugin-publish'
apply from: rootProject.file('gradle/compile/groovy.gradle')
apply from: rootProject.file('gradle/test/junit5.gradle')
dependencies {
api gradleApi()
testImplementation gradleTestKit()
}
if (project.hasProperty('gradle.publish.key')) {
gradlePlugin {
website = 'https://xbib.org/joerg/gradle-plugins/src/branch/main/gradle-plugin-c'
vcsUrl = 'https://xbib.org/joerg/gradle-plugins'
plugins {
cPlugin {
id = 'org.xbib.gradle.plugin.c'
implementationClass = 'org.xbib.gradle.plugin.c.CPlugin'
version = project.version
description = 'Gradle plugin for build with C'
displayName = 'Gradle Plugin for build with C'
tags.set(['c'])
}
}
}
}

View file

@ -0,0 +1,2 @@
name = gradle-plugin-c
version = 3.1.0

View file

@ -0,0 +1,59 @@
package org.xbib.gradle.plugin.c;
import java.io.File;
import javax.inject.Inject;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.ConfigurationContainer;
import org.gradle.api.artifacts.dsl.DependencyHandler;
import org.gradle.api.attributes.Attribute;
import org.gradle.api.file.FileCollection;
import org.gradle.api.model.ObjectFactory;
import org.gradle.nativeplatform.platform.NativePlatform;
import org.gradle.nativeplatform.platform.internal.DefaultArchitecture;
import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform;
import org.gradle.nativeplatform.platform.internal.DefaultOperatingSystem;
import org.gradle.nativeplatform.toolchain.NativeToolChain;
import org.xbib.gradle.plugin.c.toolchain.CNativeToolChain;
public abstract class CExtension {
static final Attribute<Boolean> EXTRACTED_TOOLS_ATTRIBUTE = Attribute.of("extracted-tools", Boolean.class);
static final NativePlatform GENERIC_NATIVE_PLATFORM = new DefaultNativePlatform(
"generic_platform",
new DefaultOperatingSystem("generic_os"),
new DefaultArchitecture("generic_arch")
);
public NativeToolChain localTool(String version, String location, String objectFileExtension) {
File tool = new File(location);
return getObjects().newInstance(CNativeToolChain.class, version, tool.getName(),
getObjects().fileCollection().from(tool), objectFileExtension);
}
public NativeToolChain repositoryTool(String group,
String name,
String version,
String locationInZip,
String objectFileExtension) {
Configuration toolConfiguration = getConfigurations().detachedConfiguration(
getDependencies().create(group + ":" + name + ":" + version + "@zip"));
FileCollection tool = toolConfiguration.getIncoming().artifactView(a ->
a.getAttributes().attribute(EXTRACTED_TOOLS_ATTRIBUTE, true))
.getFiles().getAsFileTree().filter(file -> file.getPath().endsWith(locationInZip));
return getObjects().newInstance(CNativeToolChain.class, name, version, tool, objectFileExtension);
}
public NativePlatform platform() {
return GENERIC_NATIVE_PLATFORM;
}
@Inject
abstract protected ObjectFactory getObjects();
@Inject
abstract protected ConfigurationContainer getConfigurations();
@Inject
abstract protected DependencyHandler getDependencies();
}

View file

@ -0,0 +1,24 @@
package org.xbib.gradle.plugin.c;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.xbib.gradle.plugin.c.toolchain.transform.ExtractZipTransform;
import static org.gradle.api.artifacts.type.ArtifactTypeDefinition.ARTIFACT_TYPE_ATTRIBUTE;
@SuppressWarnings("unused")
abstract public class CPlugin implements Plugin<Project> {
@Override
public void apply(final Project project) {
// Register extension
project.getExtensions().create("c", CExtension.class);
// Make the ExtractZipTransform known
project.getDependencies().getAttributesSchema().attribute(CExtension.EXTRACTED_TOOLS_ATTRIBUTE);
project.getDependencies().getArtifactTypes().maybeCreate("zip")
.getAttributes().attribute(CExtension.EXTRACTED_TOOLS_ATTRIBUTE, false);
project.getDependencies().registerTransform(ExtractZipTransform.class, t -> {
t.getFrom().attribute(CExtension.EXTRACTED_TOOLS_ATTRIBUTE, false).attribute(ARTIFACT_TYPE_ATTRIBUTE, "zip");
t.getTo().attribute(CExtension.EXTRACTED_TOOLS_ATTRIBUTE, true).attribute(ARTIFACT_TYPE_ATTRIBUTE, "zip");
});
}
}

View file

@ -0,0 +1,31 @@
package org.xbib.gradle.plugin.c.tasks;
import java.util.List;
import org.gradle.api.provider.MapProperty;
import org.gradle.api.tasks.CacheableTask;
import org.gradle.api.tasks.Input;
import org.gradle.language.c.tasks.CCompile;
import org.gradle.nativeplatform.toolchain.internal.NativeCompileSpec;
/**
* Extension of the 'org.gradle.language.c.tasks.CCompile' that supports defining different compiler arguments
* for different source files.
*/
@CacheableTask
public abstract class ExtendedCCompile extends CCompile {
@Input
public abstract MapProperty<String, List<String>> getPerFileCompilerArgs();
@Override
protected NativeCompileSpec createCompileSpec() {
return new ExtendedCCompileSpec();
}
@Override
protected void configureSpec(NativeCompileSpec spec) {
super.configureSpec(spec);
ExtendedCCompileSpec extendedCCompileSpec = (ExtendedCCompileSpec) spec;
extendedCCompileSpec.getPerFileCompilerArgs().putAll(getPerFileCompilerArgs().get());
}
}

View file

@ -0,0 +1,15 @@
package org.xbib.gradle.plugin.c.tasks;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.gradle.language.c.internal.DefaultCCompileSpec;
public class ExtendedCCompileSpec extends DefaultCCompileSpec {
private final Map<String, List<String>> perFileCompilerArgs = new HashMap<>();
public Map<String, List<String>> getPerFileCompilerArgs() {
return perFileCompilerArgs;
}
}

View file

@ -0,0 +1,80 @@
package org.xbib.gradle.plugin.c.tasks;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import org.gradle.api.file.ConfigurableFileCollection;
import org.gradle.api.tasks.CacheableTask;
import org.gradle.api.tasks.TaskAction;
import org.gradle.api.tasks.WorkResult;
import org.gradle.internal.operations.logging.BuildOperationLogger;
import org.gradle.language.assembler.internal.DefaultAssembleSpec;
import org.gradle.language.assembler.tasks.Assemble;
import org.gradle.language.base.internal.compile.Compiler;
import org.gradle.language.base.internal.tasks.StaleOutputCleaner;
import org.gradle.nativeplatform.internal.BuildOperationLoggingCompilerDecorator;
import org.gradle.nativeplatform.platform.internal.NativePlatformInternal;
import org.gradle.nativeplatform.toolchain.internal.NativeToolChainInternal;
import org.gradle.nativeplatform.toolchain.internal.compilespec.AssembleSpec;
import org.gradle.work.ChangeType;
import org.gradle.work.FileChange;
import org.gradle.work.Incremental;
import org.gradle.work.InputChanges;
/**
* Incremental and cacheable version of the 'org.gradle.language.assembler.tasks.Assemble' task.
*/
@CacheableTask
public abstract class IncrementalAssemble extends Assemble {
@Incremental
@Override
public ConfigurableFileCollection getSource() {
return super.getSource();
}
@TaskAction
public void assemble(InputChanges inputs) {
// Copied from super class and adjusted for incremental assembling
BuildOperationLogger operationLogger = getOperationLoggerFactory().newOperationLogger(getName(), getTemporaryDir());
List<File> toDelete = new ArrayList<>();
List<File> toAssemble = new ArrayList<>();
for (FileChange change : inputs.getFileChanges(super.getSource())) {
if (change.getChangeType() == ChangeType.REMOVED) {
toDelete.add(change.getFile());
}
if (change.getChangeType() != ChangeType.REMOVED) {
toAssemble.add(change.getFile());
}
}
boolean cleanedOutputs = StaleOutputCleaner.cleanOutputs(
getDeleter(),
toDelete,
getObjectFileDir()
);
DefaultAssembleSpec spec = new DefaultAssembleSpec();
spec.setTempDir(getTemporaryDir());
spec.setObjectFileDir(getObjectFileDir());
spec.source(toAssemble);
spec.include(getIncludes());
spec.args(getAssemblerArgs());
spec.setOperationLogger(operationLogger);
NativeToolChainInternal nativeToolChain = (NativeToolChainInternal) getToolChain().get();
NativePlatformInternal nativePlatform = (NativePlatformInternal) getTargetPlatform().get();
Compiler<AssembleSpec> compiler = nativeToolChain.select(nativePlatform).newCompiler(AssembleSpec.class);
WorkResult result = BuildOperationLoggingCompilerDecorator.wrap(compiler).execute(spec);
setDidWork(result.getDidWork() || cleanedOutputs);
}
@Override
public void assemble() {
// disable assemble() without 'inputs' argument
}
}

View file

@ -0,0 +1,66 @@
package org.xbib.gradle.plugin.c.toolchain;
import javax.inject.Inject;
import org.gradle.api.NonNullApi;
import org.gradle.api.file.FileCollection;
import org.gradle.api.model.ObjectFactory;
import org.gradle.nativeplatform.platform.internal.NativePlatformInternal;
import org.gradle.nativeplatform.toolchain.NativeToolChain;
import org.gradle.nativeplatform.toolchain.internal.NativeLanguage;
import org.gradle.nativeplatform.toolchain.internal.NativeToolChainInternal;
import org.gradle.nativeplatform.toolchain.internal.PlatformToolProvider;
@NonNullApi
public abstract class CNativeToolChain implements NativeToolChain, NativeToolChainInternal {
private final String toolName;
private final String toolVersion;
private final FileCollection tool;
private final String objectFileExtension;
@Inject
public CNativeToolChain(String toolName,
String toolVersion,
FileCollection tool,
String objectFileExtension) {
this.toolName = toolName;
this.toolVersion = toolVersion;
this.tool = tool;
this.objectFileExtension = objectFileExtension;
}
@Override
public String getName() {
return toolName + "_" + toolVersion;
}
@Override
public PlatformToolProvider select(NativePlatformInternal nativePlatform) {
return getObjects().newInstance(CPlatformToolProvider.class, tool, toolVersion, objectFileExtension);
}
@Override
public PlatformToolProvider select(NativeLanguage nativeLanguage, NativePlatformInternal nativePlatform) {
return select(nativePlatform);
}
@Override
public String getDisplayName() {
return getName();
}
@Override
public String getOutputType() {
return getName();
}
@Override
public void assertSupported() {
}
@Inject
abstract protected ObjectFactory getObjects();
}

View file

@ -0,0 +1,202 @@
package org.xbib.gradle.plugin.c.toolchain;
import javax.inject.Inject;
import org.gradle.api.file.FileCollection;
import org.gradle.internal.logging.text.DiagnosticsVisitor;
import org.gradle.internal.operations.BuildOperationExecutor;
import org.gradle.internal.work.WorkerLeaseService;
import org.gradle.language.base.internal.compile.CompileSpec;
import org.gradle.language.base.internal.compile.Compiler;
import org.gradle.language.base.internal.compile.DefaultCompilerVersion;
import org.gradle.language.base.internal.compile.VersionAwareCompiler;
import org.gradle.nativeplatform.internal.CompilerOutputFileNamingSchemeFactory;
import org.gradle.nativeplatform.internal.LinkerSpec;
import org.gradle.nativeplatform.internal.StaticLibraryArchiverSpec;
import org.gradle.nativeplatform.toolchain.internal.CommandLineToolContext;
import org.gradle.nativeplatform.toolchain.internal.CommandLineToolInvocationWorker;
import org.gradle.nativeplatform.toolchain.internal.DefaultCommandLineToolInvocationWorker;
import org.gradle.nativeplatform.toolchain.internal.DefaultMutableCommandLineToolContext;
import org.gradle.nativeplatform.toolchain.internal.EmptySystemLibraries;
import org.gradle.nativeplatform.toolchain.internal.OutputCleaningCompiler;
import org.gradle.nativeplatform.toolchain.internal.PlatformToolProvider;
import org.gradle.nativeplatform.toolchain.internal.SystemLibraries;
import org.gradle.nativeplatform.toolchain.internal.ToolType;
import org.gradle.nativeplatform.toolchain.internal.compilespec.AssembleSpec;
import org.gradle.nativeplatform.toolchain.internal.compilespec.CCompileSpec;
import org.gradle.nativeplatform.toolchain.internal.metadata.CompilerMetadata;
import org.gradle.nativeplatform.toolchain.internal.tools.CommandLineToolSearchResult;
import org.gradle.process.internal.ExecActionFactory;
import org.gradle.util.internal.VersionNumber;
import org.xbib.gradle.plugin.c.toolchain.tools.Assembler;
import org.xbib.gradle.plugin.c.toolchain.tools.CCompiler;
import org.xbib.gradle.plugin.c.toolchain.tools.Linker;
import org.xbib.gradle.plugin.c.toolchain.tools.StaticLibraryArchiver;
public abstract class CPlatformToolProvider implements PlatformToolProvider {
private final FileCollection tool;
private final String toolVersion;
private final String objectFileExtension;
@Inject
public CPlatformToolProvider(FileCollection tool, String toolVersion, String objectFileExtension) {
this.tool = tool;
this.toolVersion = toolVersion;
this.objectFileExtension = objectFileExtension;
}
@SuppressWarnings("unchecked")
@Override
public <T extends CompileSpec> Compiler<T> newCompiler(Class<T> spec) {
if (CCompileSpec.class.isAssignableFrom(spec)) {
return (Compiler<T>) createCCompiler();
}
if (AssembleSpec.class.isAssignableFrom(spec)) {
return (Compiler<T>) createAssembler();
}
if (LinkerSpec.class.isAssignableFrom(spec)) {
return (Compiler<T>) createLinker();
}
if (StaticLibraryArchiverSpec.class.isAssignableFrom(spec)) {
return (Compiler<T>) createStaticLibraryArchiver();
}
throw new IllegalArgumentException(String.format("Don't know how to compile from a spec of type %s.",
spec.getSimpleName()));
}
protected Compiler<CCompileSpec> createCCompiler() {
CCompiler cCompiler = new CCompiler(getBuildOperationExecutor(), getCompilerOutputFileNamingSchemeFactory(),
commandLineTool("Compiler"), context(), objectFileExtension, getWorkerLeaseService());
OutputCleaningCompiler<CCompileSpec> outputCleaningCompiler = new OutputCleaningCompiler<>(cCompiler,
getCompilerOutputFileNamingSchemeFactory(), objectFileExtension);
return versionAwareCompiler(outputCleaningCompiler, "C Compiler");
}
protected Compiler<AssembleSpec> createAssembler() {
return new Assembler(getBuildOperationExecutor(), getCompilerOutputFileNamingSchemeFactory(),
commandLineTool("Assembler"), context(), objectFileExtension, getWorkerLeaseService());
}
protected Compiler<LinkerSpec> createLinker() {
Linker linker = new Linker(getBuildOperationExecutor(), commandLineTool("Linked"), context(),
getWorkerLeaseService());
return versionAwareCompiler(linker, "Linker");
}
protected Compiler<StaticLibraryArchiverSpec> createStaticLibraryArchiver() {
return new StaticLibraryArchiver(getBuildOperationExecutor(),
commandLineTool("Static Library"), context(), getWorkerLeaseService());
}
private <T extends CompileSpec> VersionAwareCompiler<T> versionAwareCompiler(Compiler<T> compiler, String toolType) {
return new VersionAwareCompiler<>(compiler, new DefaultCompilerVersion(toolType,
tool.getSingleFile().getName(), VersionNumber.parse(toolVersion)));
}
private CommandLineToolInvocationWorker commandLineTool(String name) {
return new DefaultCommandLineToolInvocationWorker(name, tool.getSingleFile(), getExecActionFactory());
}
private CommandLineToolContext context() {
return new DefaultMutableCommandLineToolContext();
}
@Override
public String getExecutableName(String executablePath) {
return executablePath;
}
@Override
public String getSharedLibraryName(String libraryPath) {
return libraryPath;
}
@Override
public String getImportLibraryName(String libraryPath) {
return libraryPath;
}
@Override
public String getSharedLibraryLinkFileName(String libraryPath) {
return libraryPath;
}
@Override
public String getStaticLibraryName(String libraryPath) {
return libraryPath;
}
@Override
public String getExecutableSymbolFileName(String executablePath) {
return executablePath;
}
@Override
public String getLibrarySymbolFileName(String libraryPath) {
return libraryPath;
}
@Override
public SystemLibraries getSystemLibraries(ToolType compilerType) {
return new EmptySystemLibraries();
}
@Override
public boolean isAvailable() {
return true;
}
@Override
public boolean isSupported() {
return true;
}
@Override
public boolean producesImportLibrary() {
return false;
}
@Override
public boolean requiresDebugBinaryStripping() {
return true;
}
@Override
public void explain(DiagnosticsVisitor visitor) {
}
@Override
public String getObjectFileExtension() {
throw new UnsupportedOperationException();
}
@Override
public <T> T get(Class<T> toolType) {
throw new UnsupportedOperationException();
}
@Override
public CompilerMetadata getCompilerMetadata(ToolType compilerType) {
throw new UnsupportedOperationException();
}
@Override
public CommandLineToolSearchResult locateTool(ToolType compilerType) {
throw new UnsupportedOperationException();
}
@Inject
protected abstract BuildOperationExecutor getBuildOperationExecutor();
@Inject
protected abstract CompilerOutputFileNamingSchemeFactory getCompilerOutputFileNamingSchemeFactory();
@Inject
protected abstract ExecActionFactory getExecActionFactory();
@Inject
protected abstract WorkerLeaseService getWorkerLeaseService();
}

View file

@ -0,0 +1,84 @@
package org.xbib.gradle.plugin.c.toolchain.tools;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.gradle.internal.Transformers;
import org.gradle.internal.operations.BuildOperationExecutor;
import org.gradle.internal.process.ArgWriter;
import org.gradle.internal.work.WorkerLeaseService;
import org.gradle.nativeplatform.internal.CompilerOutputFileNamingSchemeFactory;
import org.gradle.nativeplatform.toolchain.internal.CommandLineToolContext;
import org.gradle.nativeplatform.toolchain.internal.CommandLineToolInvocation;
import org.gradle.nativeplatform.toolchain.internal.CommandLineToolInvocationWorker;
import org.gradle.nativeplatform.toolchain.internal.NativeCompiler;
import org.gradle.nativeplatform.toolchain.internal.compilespec.AssembleSpec;
public class Assembler extends NativeCompiler<AssembleSpec> {
private final String objectFileExtension;
public Assembler(BuildOperationExecutor buildOperationExecutor,
CompilerOutputFileNamingSchemeFactory compilerOutputFileNamingSchemeFactory,
CommandLineToolInvocationWorker commandLineTool,
CommandLineToolContext invocationContext,
String objectFileExtension,
WorkerLeaseService workerLeaseService) {
super(buildOperationExecutor, compilerOutputFileNamingSchemeFactory, commandLineTool, invocationContext,
new AssemblerArgsTransformer(), Transformers.noOpTransformer(),
objectFileExtension, true,
workerLeaseService);
this.objectFileExtension = objectFileExtension;
}
@Override
protected CommandLineToolInvocation createPerFileInvocation(List<String> genericArgs,
File sourceFile,
File objectDir,
AssembleSpec spec) {
List<String> sourceArgs = this.getSourceArgs(sourceFile);
List<String> outputArgs = this.getOutputArgs(spec, this.getOutputFileDir(sourceFile, objectDir,
objectFileExtension));
List<String> pchArgs = this.maybeGetPCHArgs(spec, sourceFile);
// Changed order: put 'outputArgs' first
return this.newInvocation("compiling ".concat(sourceFile.getName()), objectDir,
this.buildPerFileArgs(outputArgs, genericArgs, sourceArgs, pchArgs), spec.getOperationLogger());
}
@Override
protected Iterable<String> buildPerFileArgs(List<String> genericArgs,
List<String> sourceArgs,
List<String> outputArgs,
List<String> pchArgs) {
if (pchArgs != null && !pchArgs.isEmpty()) {
throw new UnsupportedOperationException("Precompiled header arguments cannot be specified for an Assembler compiler.");
}
return super.buildPerFileArgs(genericArgs, sourceArgs, outputArgs, pchArgs);
}
@Override
protected List<String> getOutputArgs(AssembleSpec spec, File outputFile) {
return Arrays.asList("-o", outputFile.getAbsolutePath());
}
@Override
protected void addOptionsFileArgs(List<String> args, File tempDir) {
ArrayList<String> originalArgs = new ArrayList<>(args);
args.clear();
args.addAll(ArgWriter.argsFileGenerator(new File(tempDir, "options.txt"),
ArgWriter.unixStyleFactory()).transform(originalArgs));
}
@Override
protected List<String> getPCHArgs(AssembleSpec spec) {
List<String> pchArgs = new ArrayList<>();
if (spec.getPrefixHeaderFile() != null) {
pchArgs.add("-include");
pchArgs.add(spec.getPrefixHeaderFile().getAbsolutePath());
}
return pchArgs;
}
}

View file

@ -0,0 +1,15 @@
package org.xbib.gradle.plugin.c.toolchain.tools;
import java.util.List;
import org.gradle.api.NonNullApi;
import org.gradle.nativeplatform.toolchain.internal.ArgsTransformer;
import org.gradle.nativeplatform.toolchain.internal.compilespec.AssembleSpec;
@NonNullApi
public class AssemblerArgsTransformer implements ArgsTransformer<AssembleSpec> {
@Override
public List<String> transform(AssembleSpec assembleSpec) {
return assembleSpec.getAllArgs();
}
}

View file

@ -0,0 +1,70 @@
package org.xbib.gradle.plugin.c.toolchain.tools;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.gradle.internal.Transformers;
import org.gradle.internal.operations.BuildOperationExecutor;
import org.gradle.internal.process.ArgWriter;
import org.gradle.internal.work.WorkerLeaseService;
import org.gradle.nativeplatform.internal.CompilerOutputFileNamingSchemeFactory;
import org.gradle.nativeplatform.toolchain.internal.CommandLineToolContext;
import org.gradle.nativeplatform.toolchain.internal.CommandLineToolInvocation;
import org.gradle.nativeplatform.toolchain.internal.CommandLineToolInvocationWorker;
import org.gradle.nativeplatform.toolchain.internal.NativeCompiler;
import org.gradle.nativeplatform.toolchain.internal.compilespec.CCompileSpec;
import org.xbib.gradle.plugin.c.tasks.ExtendedCCompileSpec;
public class CCompiler extends NativeCompiler<CCompileSpec> {
public CCompiler(BuildOperationExecutor buildOperationExecutor,
CompilerOutputFileNamingSchemeFactory compilerOutputFileNamingSchemeFactory,
CommandLineToolInvocationWorker commandLineToolInvocationWorker,
CommandLineToolContext invocationContext,
String objectFileExtension,
WorkerLeaseService workerLeaseService) {
super(buildOperationExecutor, compilerOutputFileNamingSchemeFactory, commandLineToolInvocationWorker, invocationContext,
new CCompilerArgsTransformer(), Transformers.noOpTransformer(),
objectFileExtension, true, workerLeaseService);
}
@Override
protected List<String> getOutputArgs(CCompileSpec spec, File outputFile) {
return Arrays.asList("-o", outputFile.getAbsolutePath());
}
@Override
protected void addOptionsFileArgs(List<String> args, File tempDir) {
ArrayList<String> originalArgs = new ArrayList<>(args);
args.clear();
args.addAll(ArgWriter.argsFileGenerator(new File(tempDir, "options.txt"),
ArgWriter.unixStyleFactory()).transform(originalArgs));
}
@Override
protected List<String> getPCHArgs(CCompileSpec spec) {
List<String> pchArgs = new ArrayList<>();
if (spec.getPrefixHeaderFile() != null) {
pchArgs.add("-include");
pchArgs.add(spec.getPrefixHeaderFile().getAbsolutePath());
}
return pchArgs;
}
@Override
protected CommandLineToolInvocation createPerFileInvocation(List<String> genericArgs,
File sourceFile,
File objectDir,
CCompileSpec spec) {
ExtendedCCompileSpec extendedCCompileSpec = (ExtendedCCompileSpec) spec;
List<String> perFileArgs = extendedCCompileSpec.getPerFileCompilerArgs().get(sourceFile.getName());
if (perFileArgs != null) {
List<String> allPerFileArgs = new ArrayList<>(genericArgs);
allPerFileArgs.addAll(perFileArgs);
return super.createPerFileInvocation(allPerFileArgs, sourceFile, objectDir, spec);
} else {
return super.createPerFileInvocation(genericArgs, sourceFile, objectDir, spec);
}
}
}

View file

@ -0,0 +1,35 @@
package org.xbib.gradle.plugin.c.toolchain.tools;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import org.gradle.api.NonNullApi;
import org.gradle.nativeplatform.toolchain.internal.ArgsTransformer;
import org.gradle.nativeplatform.toolchain.internal.MacroArgsConverter;
import org.gradle.nativeplatform.toolchain.internal.compilespec.CCompileSpec;
@NonNullApi
public class CCompilerArgsTransformer implements ArgsTransformer<CCompileSpec> {
@Override
public List<String> transform(CCompileSpec spec) {
List<String> args = new ArrayList<>();
for (File file : spec.getIncludeRoots()) {
args.add("-I");
args.add(file.getAbsolutePath());
}
for (File file : spec.getSystemIncludeRoots()) {
args.add("-isystem");
args.add(file.getAbsolutePath());
}
for (String macroArg : new MacroArgsConverter().transform(spec.getMacros())) {
args.add("-D" + macroArg);
}
args.addAll(spec.getAllArgs());
return args;
}
}

View file

@ -0,0 +1,46 @@
package org.xbib.gradle.plugin.c.toolchain.tools;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import org.gradle.api.Action;
import org.gradle.internal.operations.BuildOperationExecutor;
import org.gradle.internal.operations.BuildOperationQueue;
import org.gradle.internal.process.ArgWriter;
import org.gradle.internal.work.WorkerLeaseService;
import org.gradle.nativeplatform.internal.LinkerSpec;
import org.gradle.nativeplatform.toolchain.internal.AbstractCompiler;
import org.gradle.nativeplatform.toolchain.internal.CommandLineToolContext;
import org.gradle.nativeplatform.toolchain.internal.CommandLineToolInvocation;
import org.gradle.nativeplatform.toolchain.internal.CommandLineToolInvocationWorker;
public class Linker extends AbstractCompiler<LinkerSpec> {
public Linker(BuildOperationExecutor buildOperationExecutor,
CommandLineToolInvocationWorker commandLineToolInvocationWorker,
CommandLineToolContext invocationContext,
WorkerLeaseService workerLeaseService) {
super(buildOperationExecutor, commandLineToolInvocationWorker, invocationContext,
new LinkerArgsTransformer(),
true, workerLeaseService);
}
@Override
protected Action<BuildOperationQueue<CommandLineToolInvocation>> newInvocationAction(final LinkerSpec spec,
List<String> args) {
final CommandLineToolInvocation invocation = newInvocation("linking " + spec.getOutputFile().getName(),
args, spec.getOperationLogger());
return buildQueue -> {
buildQueue.setLogLocation(spec.getOperationLogger().getLogLocation());
buildQueue.add(invocation);
};
}
@Override
protected void addOptionsFileArgs(List<String> args, File tempDir) {
ArrayList<String> originalArgs = new ArrayList<>(args);
args.clear();
args.addAll(ArgWriter.argsFileGenerator(new File(tempDir, "options.txt"),
ArgWriter.unixStyleFactory()).transform(originalArgs));
}
}

View file

@ -0,0 +1,36 @@
package org.xbib.gradle.plugin.c.toolchain.tools;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import org.gradle.api.NonNullApi;
import org.gradle.nativeplatform.internal.LinkerSpec;
import org.gradle.nativeplatform.toolchain.internal.ArgsTransformer;
@NonNullApi
public class LinkerArgsTransformer implements ArgsTransformer<LinkerSpec> {
@Override
public List<String> transform(LinkerSpec spec) {
List<String> args = new ArrayList<>(spec.getSystemArgs());
args.addAll(spec.getArgs());
for (File file : spec.getObjectFiles()) {
args.add(file.getAbsolutePath());
}
for (File file : spec.getLibraries()) {
args.add(file.getAbsolutePath());
}
args.add("-o");
args.add(spec.getOutputFile().getAbsolutePath());
if (!spec.getLibraryPath().isEmpty()) {
throw new UnsupportedOperationException("Library Path not yet supported");
}
return args;
}
}

View file

@ -0,0 +1,59 @@
package org.xbib.gradle.plugin.c.toolchain.tools;
import java.io.File;
import java.util.List;
import org.gradle.api.Action;
import org.gradle.api.GradleException;
import org.gradle.api.tasks.WorkResult;
import org.gradle.internal.operations.BuildOperationExecutor;
import org.gradle.internal.operations.BuildOperationQueue;
import org.gradle.internal.work.WorkerLeaseService;
import org.gradle.nativeplatform.internal.StaticLibraryArchiverSpec;
import org.gradle.nativeplatform.toolchain.internal.AbstractCompiler;
import org.gradle.nativeplatform.toolchain.internal.CommandLineToolContext;
import org.gradle.nativeplatform.toolchain.internal.CommandLineToolInvocation;
import org.gradle.nativeplatform.toolchain.internal.CommandLineToolInvocationWorker;
public class StaticLibraryArchiver extends AbstractCompiler<StaticLibraryArchiverSpec> {
public StaticLibraryArchiver(BuildOperationExecutor buildOperationExecutor,
CommandLineToolInvocationWorker commandLineToolInvocationWorker,
CommandLineToolContext invocationContext,
WorkerLeaseService workerLeaseService) {
super(buildOperationExecutor, commandLineToolInvocationWorker, invocationContext,
new StaticLibraryArchiverArgsTransformer(), false, workerLeaseService);
}
@Override
public WorkResult execute(final StaticLibraryArchiverSpec spec) {
deletePreviousOutput(spec);
return super.execute(spec);
}
private void deletePreviousOutput(StaticLibraryArchiverSpec spec) {
// Need to delete the previous archive, otherwise stale object files will remain
if (!spec.getOutputFile().isFile()) {
return;
}
if (!(spec.getOutputFile().delete())) {
throw new GradleException("Create static archive failed: could not delete previous archive");
}
}
@Override
protected Action<BuildOperationQueue<CommandLineToolInvocation>> newInvocationAction(final StaticLibraryArchiverSpec spec,
List<String> args) {
final CommandLineToolInvocation invocation = newInvocation(
"archiving " + spec.getOutputFile().getName(), spec.getOutputFile().getParentFile(), args, spec.getOperationLogger());
return buildQueue -> {
buildQueue.setLogLocation(spec.getOperationLogger().getLogLocation());
buildQueue.add(invocation);
};
}
@Override
protected void addOptionsFileArgs(List<String> args, File tempDir) {
// No support for command file
}
}

View file

@ -0,0 +1,26 @@
package org.xbib.gradle.plugin.c.toolchain.tools;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import org.gradle.api.NonNullApi;
import org.gradle.nativeplatform.internal.StaticLibraryArchiverSpec;
import org.gradle.nativeplatform.toolchain.internal.ArgsTransformer;
@NonNullApi
public class StaticLibraryArchiverArgsTransformer implements ArgsTransformer<StaticLibraryArchiverSpec> {
public List<String> transform(StaticLibraryArchiverSpec spec) {
List<String> args = new ArrayList<>(spec.getAllArgs());
args.add("-rv");
args.add(spec.getOutputFile().getAbsolutePath());
for (File file : spec.getObjectFiles()) {
args.add(file.getAbsolutePath());
}
return args;
}
}

View file

@ -0,0 +1,63 @@
package org.xbib.gradle.plugin.c.toolchain.transform;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.attribute.PosixFilePermissions;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import org.gradle.api.artifacts.transform.InputArtifact;
import org.gradle.api.artifacts.transform.TransformAction;
import org.gradle.api.artifacts.transform.TransformOutputs;
import org.gradle.api.artifacts.transform.TransformParameters;
import org.gradle.api.file.FileSystemLocation;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.PathSensitive;
import org.gradle.api.tasks.PathSensitivity;
import org.gradle.internal.os.OperatingSystem;
public abstract class ExtractZipTransform implements TransformAction<TransformParameters.None> {
@InputArtifact
@PathSensitive(PathSensitivity.RELATIVE)
public abstract Provider<FileSystemLocation> getInputArtifact();
@Override
public void transform(TransformOutputs outputs) {
File input = getInputArtifact().get().getAsFile();
File unzipDir = outputs.dir(input.getName().substring(0, input.getName().lastIndexOf(".zip")));
try {
unzipTo(input, unzipDir);
System.out.println("Transformed into: " + unzipDir.getAbsolutePath());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private void unzipTo(File input, File unzipDir) throws IOException {
byte[] buffer = new byte[1024];
ZipInputStream zis = new ZipInputStream(Files.newInputStream(input.toPath()));
ZipEntry zipEntry = zis.getNextEntry();
while (zipEntry != null) {
if (!zipEntry.isDirectory()) {
File newFile = new File(unzipDir, zipEntry.getName());
newFile.getParentFile().mkdirs();
FileOutputStream fos = new FileOutputStream(newFile);
int len;
while ((len = zis.read(buffer)) > 0) {
fos.write(buffer, 0, len);
}
fos.close();
if (OperatingSystem.current().isUnix()) {
Files.setPosixFilePermissions(newFile.toPath(), PosixFilePermissions.fromString("rwxr-xr-x"));
}
}
zipEntry = zis.getNextEntry();
}
zis.closeEntry();
zis.close();
}
}

View file

@ -0,0 +1,61 @@
package org.xbib.gradle.plugin.c.test
import org.gradle.testkit.runner.BuildResult
import org.gradle.testkit.runner.GradleRunner
import org.gradle.testkit.runner.TaskOutcome
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.io.TempDir
import static org.junit.jupiter.api.Assertions.assertEquals
class CPluginIntegrationTest {
private File projectDir
private File settingsFile
private File buildFile
@BeforeEach
void setup(@TempDir File testProjectDir) throws IOException {
this.projectDir = testProjectDir
this.settingsFile = new File(testProjectDir, "settings.gradle")
this.buildFile = new File(testProjectDir, "build.gradle")
}
@Test
void testPlugin() {
String settingsFileContent = '''
rootProject.name = 'c-test'
'''
settingsFile.write(settingsFileContent)
String buildFileContent = '''
plugins {
id 'base'
id 'org.xbib.gradle.plugin.c'
}
// dummy task, no source present
tasks.register('compileC', org.xbib.gradle.plugin.c.tasks.ExtendedCCompile) {
toolChain.set(c.localTool("14.0.0", "/usr/bin/clang", ".o"))
targetPlatform.set(c.platform())
source(layout.projectDirectory.files("src/c").asFileTree)
includes(layout.projectDirectory.files("src/headers"))
compilerArgs.add("-S")
objectFileDir.set(layout.buildDirectory.dir("out/o"))
}
'''
buildFile.write(buildFileContent)
BuildResult result = GradleRunner.create()
.withProjectDir(projectDir)
.withPluginClasspath()
.withArguments(":compileC", "--info", "--stacktrace", "--warning-mode=all")
.forwardOutput()
.build()
assertEquals(TaskOutcome.NO_SOURCE, result.task(":compileC").getOutcome())
}
}

View file

@ -0,0 +1,8 @@
This work is based on
https://github.com/crimsonmagick/gradle-cmake-plugin
as of Januar 8, 2024
License: Apache 2.0

View file

@ -0,0 +1,185 @@
# gradle-plugin-cmake
This plugin allows to configure and build using CMake.
## Prerequisites
* `CMake` installed on the system. Available [here](https://www.cmake.org "CMake Homepage").
## To apply the plugin:
**plugins DSL**
```groovy
plugins {
id 'org.xbib.gradle.plugin.cmake' version '3.1.0'
}
```
**Legacy plugin application**
```groovy
buildscript {
repositories {
maven {
url "https://plugins.gradle.org/m2/"
}
}
dependencies {
classpath 'org.xbib.gradle:gradle-plugin-cmake:3.1.0'
}
}
apply plugin: "org.xbib.gradle.plugin.cmake"
```
and configure by:
```groovy
cmake {
// optional configration to path of cmake. Not required if cmake is on the path.
executable='/my/path/to/cmake'
// optional working folder. default is ./build/cmake
workingFolder=file("$buildDir/cmake")
// cmakeConfigure parameters
// optional source folder. This is where the main CMakeLists.txt file resides. Default is ./src/main/cpp
sourceFolder=file("$projectDir/src/main/cpp")
// optional install prefix. By default, install prefix is empty.
installPrefix="${System.properties['user.home']}"
// select a generator (optional, otherwise cmake's default generator is used)
generator='Visual Studio 15 2017'
// set a platform for generators that support it (usually Visual Studio)
platform='x64'
// set a toolset generators that support it (usually only Visual Studio)
toolset='v141'
// optionally set to build static libs
buildStaticLibs=true
// optionally set to build shared libs
buildSharedLibs=true
// define arbitrary CMake parameters. The below adds -Dtest=hello to cmake command line.
defs.test='hello'
// cmakeBuild parameters
// optional configuration to build
buildConfig='Release'
// optional build target
buildTarget='install'
// optional build clean. if set to true, calls cmake --build with --clean-first
buildClean=false
}
```
## Auto-created tasks
* *cmakeConfigure*: Calls CMake to generate your build scripts in the folder selected by workingFolder.
* *cmakeBuild*: Calls CMake --build in the folder selected by workingFolder to actually build.
* *cmakeClean*: Cleans the workingFolder.
* *cmakeGenerators*: Trys to list the generators available on the current platform by parsing `cmake --help`'s output.
## Examples
clean, configure and build:
```bash
./gradlew cmakeClean cmakeConfigure cmakeBuild
```
if you have assemble and clean tasks in your gradle project already you can also use:
```bash
assemble.dependsOn cmakeBuild
cmakeBuild.dependsOn cmakeConfigure
clean.dependsOn cmakeClean
```
and just call
```bash
./gradlew clean assemble
```
If you want to get the output of cmake, add -i to your gradle call, for example:
```bash
./gradlew cmakeConfigure -i
```
## Custom tasks
You can create custom tasks the following way:
```groovy
task configureFoo(type: org.xbib.gradle.plugin.cmake.CMakeConfigureTask) {
sourceFolder=file("$projectDir/src/main/cpp/foo")
workingFolder=file("$buildDir/cmake/foo")
// ... other parameters you need, see above, except the ones listed under cmakeBuild Parameters
}
task buildFoo(type: org.xbib.gradle.plugin.cmake.CMakeBuildTask) {
workingFolder=file("$buildDir/cmake/foo")
// ... other parameters you need, see above, except the ones listed under cmakeConfigure parameters
}
buildFoo.dependsOn configureFoo // optional --- make sure its configured when you run the build task
```
### Multiple targets (cross-compilation)
If you need to configure for multiple targets you can use the `targets` property:
```groovy
cmake {
sourceFolder = "$projectDir/src"
buildSharedLibs = true
buildClean = true
buildConfig = 'Release'
targets {
windows {
final os = OperatingSystem.WINDOWS
workingFolder = new File(project.getBuildDir(), "cmake" + File.separator + os.nativePrefix)
platform='x64'
}
linux {
final os = OperatingSystem.LINUX
workingFolder = new File(project.getBuildDir(), "cmake" + File.separator + os.nativePrefix)
platform = 'x64'
}
mac {
final os = OperatingSystem.MAC_OS
workingFolder = new File(project.getBuildDir(), "cmake" + File.separator + os.nativePrefix)
platform = 'arm64'
}
}
}
```
### Custom tasks using main configuration
As an alternative to using `targets` you can "import" the settings you've made in the main configuration "cmake" using the 'configureFromProject()' call:
```groovy
cmake {
executable='/my/path/to/cmake'
workingFolder=file("$buildDir/cmake")
sourceFolder=file("$projectDir/src/main/cpp")
installPrefix="${System.properties['user.home']}"
generator='Visual Studio 15 2017'
platform='x64'
}
task cmakeConfigureX86(type: org.xbib.gradle.plugin.cmake.CMakeConfigureTask) {
configureFromProject() // uses everything in the cmake { ... } section.
// overwrite target platform
platform='x86'
// set a different working folder to not collide with default task
workingFolder=file("$buildDir/cmake_x86")
}
task cmakeBuildX86(type: org.xbib.gradle.plugin.cmake.CMakeBuildTask) {
configureFromProject() // uses everything in the cmake { ... } section.
workingFolder=file("$buildDir/cmake_x86")
}
cmakeBuildX86.dependsOn cmakeConfigureX86
```

View file

@ -4,7 +4,7 @@ plugins {
apply plugin: 'com.gradle.plugin-publish' apply plugin: 'com.gradle.plugin-publish'
apply from: rootProject.file('gradle/compile/java.gradle') apply from: rootProject.file('gradle/compile/groovy.gradle')
apply from: rootProject.file('gradle/test/junit5.gradle') apply from: rootProject.file('gradle/test/junit5.gradle')
dependencies { dependencies {

View file

@ -0,0 +1,53 @@
package org.xbib.gradle.plugin.cmake.test
import org.gradle.testkit.runner.BuildResult
import org.gradle.testkit.runner.GradleRunner
import org.gradle.testkit.runner.TaskOutcome
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.io.TempDir
import static org.junit.jupiter.api.Assertions.assertEquals
class CmakePluginIntegrationTest {
private File projectDir
private File settingsFile
private File buildFile
@BeforeEach
void setup(@TempDir File testProjectDir) throws IOException {
this.projectDir = testProjectDir
File f = new File(testProjectDir, 'build/src/main/cpp')
f.mkdirs()
File cmakeList = new File(f, 'CMakeLists.txt')
cmakeList.write('')
this.settingsFile = new File(testProjectDir, "settings.gradle")
this.buildFile = new File(testProjectDir, "build.gradle")
}
@Test
void testPlugin() {
String settingsFileContent = '''
rootProject.name = 'cmake-test'
'''
settingsFile.write(settingsFileContent)
String buildFileContent = '''
plugins {
id 'org.xbib.gradle.plugin.cmake'
}
'''
buildFile.write(buildFileContent)
BuildResult result = GradleRunner.create()
.withProjectDir(projectDir)
.withPluginClasspath()
.withArguments(":cmakeConfigure", "--info", "--stacktrace", "--warning-mode=all")
.forwardOutput()
.build()
assertEquals(TaskOutcome.SUCCESS, result.task(":cmakeConfigure").getOutcome())
}
}

View file

@ -4,6 +4,7 @@ dependencyResolutionManagement {
libs { libs {
version('gradle', '8.5') version('gradle', '8.5')
version('groovy', '3.0.17') // MUST match gradle groovy version version('groovy', '3.0.17') // MUST match gradle groovy version
// Java 21
version('asm', '9.6') version('asm', '9.6')
// Attention: it is impossible to develop a gradle plugin with groovy 4! // Attention: it is impossible to develop a gradle plugin with groovy 4!
// The gradle plugin publish plugin enforces java-gradle-plugin, // The gradle plugin publish plugin enforces java-gradle-plugin,
@ -42,6 +43,7 @@ dependencyResolutionManagement {
} }
include 'gradle-plugin-asciidoctor' include 'gradle-plugin-asciidoctor'
include 'gradle-plugin-c'
include 'gradle-plugin-cmake' include 'gradle-plugin-cmake'
include 'gradle-plugin-docker' include 'gradle-plugin-docker'
include 'gradle-plugin-git' include 'gradle-plugin-git'