From d616184a2e09294dd37ce4b53c72bc9a62eff2d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Prante?= Date: Sun, 11 Feb 2024 14:28:27 +0100 Subject: [PATCH] working on c and cmake plugins --- gradle-plugin-c/LICENSE.txt | 201 +++++++++++++++++ gradle-plugin-c/NOTICE.txt | 8 + gradle-plugin-c/README.md | 81 +++++++ gradle-plugin-c/build.gradle | 30 +++ gradle-plugin-c/gradle.properties | 2 + .../org/xbib/gradle/plugin/c/CExtension.java | 59 +++++ .../org/xbib/gradle/plugin/c/CPlugin.java | 24 +++ .../plugin/c/tasks/ExtendedCCompile.java | 31 +++ .../plugin/c/tasks/ExtendedCCompileSpec.java | 15 ++ .../plugin/c/tasks/IncrementalAssemble.java | 80 +++++++ .../plugin/c/toolchain/CNativeToolChain.java | 66 ++++++ .../c/toolchain/CPlatformToolProvider.java | 202 ++++++++++++++++++ .../plugin/c/toolchain/tools/Assembler.java | 84 ++++++++ .../tools/AssemblerArgsTransformer.java | 15 ++ .../plugin/c/toolchain/tools/CCompiler.java | 70 ++++++ .../tools/CCompilerArgsTransformer.java | 35 +++ .../plugin/c/toolchain/tools/Linker.java | 46 ++++ .../tools/LinkerArgsTransformer.java | 36 ++++ .../tools/StaticLibraryArchiver.java | 59 +++++ .../StaticLibraryArchiverArgsTransformer.java | 26 +++ .../transform/ExtractZipTransform.java | 63 ++++++ .../c/test/CPluginIntegrationTest.groovy | 61 ++++++ gradle-plugin-cmake/NOTICE.txt | 8 + gradle-plugin-cmake/README.md | 185 ++++++++++++++++ gradle-plugin-cmake/build.gradle | 2 +- .../test/CmakePluginIntegrationTest.groovy | 53 +++++ settings.gradle | 2 + 27 files changed, 1543 insertions(+), 1 deletion(-) create mode 100644 gradle-plugin-c/LICENSE.txt create mode 100644 gradle-plugin-c/NOTICE.txt create mode 100644 gradle-plugin-c/README.md create mode 100644 gradle-plugin-c/build.gradle create mode 100644 gradle-plugin-c/gradle.properties create mode 100644 gradle-plugin-c/src/main/java/org/xbib/gradle/plugin/c/CExtension.java create mode 100644 gradle-plugin-c/src/main/java/org/xbib/gradle/plugin/c/CPlugin.java create mode 100644 gradle-plugin-c/src/main/java/org/xbib/gradle/plugin/c/tasks/ExtendedCCompile.java create mode 100644 gradle-plugin-c/src/main/java/org/xbib/gradle/plugin/c/tasks/ExtendedCCompileSpec.java create mode 100644 gradle-plugin-c/src/main/java/org/xbib/gradle/plugin/c/tasks/IncrementalAssemble.java create mode 100644 gradle-plugin-c/src/main/java/org/xbib/gradle/plugin/c/toolchain/CNativeToolChain.java create mode 100644 gradle-plugin-c/src/main/java/org/xbib/gradle/plugin/c/toolchain/CPlatformToolProvider.java create mode 100644 gradle-plugin-c/src/main/java/org/xbib/gradle/plugin/c/toolchain/tools/Assembler.java create mode 100644 gradle-plugin-c/src/main/java/org/xbib/gradle/plugin/c/toolchain/tools/AssemblerArgsTransformer.java create mode 100644 gradle-plugin-c/src/main/java/org/xbib/gradle/plugin/c/toolchain/tools/CCompiler.java create mode 100644 gradle-plugin-c/src/main/java/org/xbib/gradle/plugin/c/toolchain/tools/CCompilerArgsTransformer.java create mode 100644 gradle-plugin-c/src/main/java/org/xbib/gradle/plugin/c/toolchain/tools/Linker.java create mode 100644 gradle-plugin-c/src/main/java/org/xbib/gradle/plugin/c/toolchain/tools/LinkerArgsTransformer.java create mode 100644 gradle-plugin-c/src/main/java/org/xbib/gradle/plugin/c/toolchain/tools/StaticLibraryArchiver.java create mode 100644 gradle-plugin-c/src/main/java/org/xbib/gradle/plugin/c/toolchain/tools/StaticLibraryArchiverArgsTransformer.java create mode 100644 gradle-plugin-c/src/main/java/org/xbib/gradle/plugin/c/toolchain/transform/ExtractZipTransform.java create mode 100644 gradle-plugin-c/src/test/groovy/org/xbib/gradle/plugin/c/test/CPluginIntegrationTest.groovy create mode 100644 gradle-plugin-cmake/NOTICE.txt create mode 100644 gradle-plugin-cmake/README.md create mode 100644 gradle-plugin-cmake/src/test/groovy/org/xbib/gradle/plugin/cmake/test/CmakePluginIntegrationTest.groovy diff --git a/gradle-plugin-c/LICENSE.txt b/gradle-plugin-c/LICENSE.txt new file mode 100644 index 0000000..8dada3e --- /dev/null +++ b/gradle-plugin-c/LICENSE.txt @@ -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. diff --git a/gradle-plugin-c/NOTICE.txt b/gradle-plugin-c/NOTICE.txt new file mode 100644 index 0000000..391f559 --- /dev/null +++ b/gradle-plugin-c/NOTICE.txt @@ -0,0 +1,8 @@ + +This work is derived from + +https://github.com/jjohannes/plain-c + +as of January 14, 2024 + +(Apache License 2.0) diff --git a/gradle-plugin-c/README.md b/gradle-plugin-c/README.md new file mode 100644 index 0000000..f0c61eb --- /dev/null +++ b/gradle-plugin-c/README.md @@ -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("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("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("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. + diff --git a/gradle-plugin-c/build.gradle b/gradle-plugin-c/build.gradle new file mode 100644 index 0000000..da7a324 --- /dev/null +++ b/gradle-plugin-c/build.gradle @@ -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']) + } + } + } +} diff --git a/gradle-plugin-c/gradle.properties b/gradle-plugin-c/gradle.properties new file mode 100644 index 0000000..718f40e --- /dev/null +++ b/gradle-plugin-c/gradle.properties @@ -0,0 +1,2 @@ +name = gradle-plugin-c +version = 3.1.0 diff --git a/gradle-plugin-c/src/main/java/org/xbib/gradle/plugin/c/CExtension.java b/gradle-plugin-c/src/main/java/org/xbib/gradle/plugin/c/CExtension.java new file mode 100644 index 0000000..bbed108 --- /dev/null +++ b/gradle-plugin-c/src/main/java/org/xbib/gradle/plugin/c/CExtension.java @@ -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 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(); +} diff --git a/gradle-plugin-c/src/main/java/org/xbib/gradle/plugin/c/CPlugin.java b/gradle-plugin-c/src/main/java/org/xbib/gradle/plugin/c/CPlugin.java new file mode 100644 index 0000000..3d893db --- /dev/null +++ b/gradle-plugin-c/src/main/java/org/xbib/gradle/plugin/c/CPlugin.java @@ -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 { + + @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"); + }); + } +} diff --git a/gradle-plugin-c/src/main/java/org/xbib/gradle/plugin/c/tasks/ExtendedCCompile.java b/gradle-plugin-c/src/main/java/org/xbib/gradle/plugin/c/tasks/ExtendedCCompile.java new file mode 100644 index 0000000..54163fc --- /dev/null +++ b/gradle-plugin-c/src/main/java/org/xbib/gradle/plugin/c/tasks/ExtendedCCompile.java @@ -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> 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()); + } +} diff --git a/gradle-plugin-c/src/main/java/org/xbib/gradle/plugin/c/tasks/ExtendedCCompileSpec.java b/gradle-plugin-c/src/main/java/org/xbib/gradle/plugin/c/tasks/ExtendedCCompileSpec.java new file mode 100644 index 0000000..07c662e --- /dev/null +++ b/gradle-plugin-c/src/main/java/org/xbib/gradle/plugin/c/tasks/ExtendedCCompileSpec.java @@ -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> perFileCompilerArgs = new HashMap<>(); + + public Map> getPerFileCompilerArgs() { + return perFileCompilerArgs; + } +} diff --git a/gradle-plugin-c/src/main/java/org/xbib/gradle/plugin/c/tasks/IncrementalAssemble.java b/gradle-plugin-c/src/main/java/org/xbib/gradle/plugin/c/tasks/IncrementalAssemble.java new file mode 100644 index 0000000..568d857 --- /dev/null +++ b/gradle-plugin-c/src/main/java/org/xbib/gradle/plugin/c/tasks/IncrementalAssemble.java @@ -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 toDelete = new ArrayList<>(); + List 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 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 + } +} diff --git a/gradle-plugin-c/src/main/java/org/xbib/gradle/plugin/c/toolchain/CNativeToolChain.java b/gradle-plugin-c/src/main/java/org/xbib/gradle/plugin/c/toolchain/CNativeToolChain.java new file mode 100644 index 0000000..d0a4e07 --- /dev/null +++ b/gradle-plugin-c/src/main/java/org/xbib/gradle/plugin/c/toolchain/CNativeToolChain.java @@ -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(); +} diff --git a/gradle-plugin-c/src/main/java/org/xbib/gradle/plugin/c/toolchain/CPlatformToolProvider.java b/gradle-plugin-c/src/main/java/org/xbib/gradle/plugin/c/toolchain/CPlatformToolProvider.java new file mode 100644 index 0000000..1e86e37 --- /dev/null +++ b/gradle-plugin-c/src/main/java/org/xbib/gradle/plugin/c/toolchain/CPlatformToolProvider.java @@ -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 Compiler newCompiler(Class spec) { + if (CCompileSpec.class.isAssignableFrom(spec)) { + return (Compiler) createCCompiler(); + } + if (AssembleSpec.class.isAssignableFrom(spec)) { + return (Compiler) createAssembler(); + } + if (LinkerSpec.class.isAssignableFrom(spec)) { + return (Compiler) createLinker(); + } + if (StaticLibraryArchiverSpec.class.isAssignableFrom(spec)) { + return (Compiler) createStaticLibraryArchiver(); + } + throw new IllegalArgumentException(String.format("Don't know how to compile from a spec of type %s.", + spec.getSimpleName())); + } + + protected Compiler createCCompiler() { + CCompiler cCompiler = new CCompiler(getBuildOperationExecutor(), getCompilerOutputFileNamingSchemeFactory(), + commandLineTool("Compiler"), context(), objectFileExtension, getWorkerLeaseService()); + OutputCleaningCompiler outputCleaningCompiler = new OutputCleaningCompiler<>(cCompiler, + getCompilerOutputFileNamingSchemeFactory(), objectFileExtension); + return versionAwareCompiler(outputCleaningCompiler, "C Compiler"); + } + + protected Compiler createAssembler() { + return new Assembler(getBuildOperationExecutor(), getCompilerOutputFileNamingSchemeFactory(), + commandLineTool("Assembler"), context(), objectFileExtension, getWorkerLeaseService()); + } + + protected Compiler createLinker() { + Linker linker = new Linker(getBuildOperationExecutor(), commandLineTool("Linked"), context(), + getWorkerLeaseService()); + return versionAwareCompiler(linker, "Linker"); + } + + protected Compiler createStaticLibraryArchiver() { + return new StaticLibraryArchiver(getBuildOperationExecutor(), + commandLineTool("Static Library"), context(), getWorkerLeaseService()); + } + + private VersionAwareCompiler versionAwareCompiler(Compiler 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 get(Class 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(); +} diff --git a/gradle-plugin-c/src/main/java/org/xbib/gradle/plugin/c/toolchain/tools/Assembler.java b/gradle-plugin-c/src/main/java/org/xbib/gradle/plugin/c/toolchain/tools/Assembler.java new file mode 100644 index 0000000..cb31956 --- /dev/null +++ b/gradle-plugin-c/src/main/java/org/xbib/gradle/plugin/c/toolchain/tools/Assembler.java @@ -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 { + + 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 genericArgs, + File sourceFile, + File objectDir, + AssembleSpec spec) { + List sourceArgs = this.getSourceArgs(sourceFile); + List outputArgs = this.getOutputArgs(spec, this.getOutputFileDir(sourceFile, objectDir, + objectFileExtension)); + List 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 buildPerFileArgs(List genericArgs, + List sourceArgs, + List outputArgs, + List 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 getOutputArgs(AssembleSpec spec, File outputFile) { + return Arrays.asList("-o", outputFile.getAbsolutePath()); + } + + @Override + protected void addOptionsFileArgs(List args, File tempDir) { + ArrayList originalArgs = new ArrayList<>(args); + args.clear(); + args.addAll(ArgWriter.argsFileGenerator(new File(tempDir, "options.txt"), + ArgWriter.unixStyleFactory()).transform(originalArgs)); + } + + @Override + protected List getPCHArgs(AssembleSpec spec) { + List pchArgs = new ArrayList<>(); + if (spec.getPrefixHeaderFile() != null) { + pchArgs.add("-include"); + pchArgs.add(spec.getPrefixHeaderFile().getAbsolutePath()); + } + return pchArgs; + } +} \ No newline at end of file diff --git a/gradle-plugin-c/src/main/java/org/xbib/gradle/plugin/c/toolchain/tools/AssemblerArgsTransformer.java b/gradle-plugin-c/src/main/java/org/xbib/gradle/plugin/c/toolchain/tools/AssemblerArgsTransformer.java new file mode 100644 index 0000000..ea17463 --- /dev/null +++ b/gradle-plugin-c/src/main/java/org/xbib/gradle/plugin/c/toolchain/tools/AssemblerArgsTransformer.java @@ -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 { + + @Override + public List transform(AssembleSpec assembleSpec) { + return assembleSpec.getAllArgs(); + } +} \ No newline at end of file diff --git a/gradle-plugin-c/src/main/java/org/xbib/gradle/plugin/c/toolchain/tools/CCompiler.java b/gradle-plugin-c/src/main/java/org/xbib/gradle/plugin/c/toolchain/tools/CCompiler.java new file mode 100644 index 0000000..f8bbf58 --- /dev/null +++ b/gradle-plugin-c/src/main/java/org/xbib/gradle/plugin/c/toolchain/tools/CCompiler.java @@ -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 { + + 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 getOutputArgs(CCompileSpec spec, File outputFile) { + return Arrays.asList("-o", outputFile.getAbsolutePath()); + } + + @Override + protected void addOptionsFileArgs(List args, File tempDir) { + ArrayList originalArgs = new ArrayList<>(args); + args.clear(); + args.addAll(ArgWriter.argsFileGenerator(new File(tempDir, "options.txt"), + ArgWriter.unixStyleFactory()).transform(originalArgs)); + } + + @Override + protected List getPCHArgs(CCompileSpec spec) { + List pchArgs = new ArrayList<>(); + if (spec.getPrefixHeaderFile() != null) { + pchArgs.add("-include"); + pchArgs.add(spec.getPrefixHeaderFile().getAbsolutePath()); + } + return pchArgs; + } + + @Override + protected CommandLineToolInvocation createPerFileInvocation(List genericArgs, + File sourceFile, + File objectDir, + CCompileSpec spec) { + ExtendedCCompileSpec extendedCCompileSpec = (ExtendedCCompileSpec) spec; + List perFileArgs = extendedCCompileSpec.getPerFileCompilerArgs().get(sourceFile.getName()); + if (perFileArgs != null) { + List allPerFileArgs = new ArrayList<>(genericArgs); + allPerFileArgs.addAll(perFileArgs); + return super.createPerFileInvocation(allPerFileArgs, sourceFile, objectDir, spec); + } else { + return super.createPerFileInvocation(genericArgs, sourceFile, objectDir, spec); + } + } +} diff --git a/gradle-plugin-c/src/main/java/org/xbib/gradle/plugin/c/toolchain/tools/CCompilerArgsTransformer.java b/gradle-plugin-c/src/main/java/org/xbib/gradle/plugin/c/toolchain/tools/CCompilerArgsTransformer.java new file mode 100644 index 0000000..3e3a7a7 --- /dev/null +++ b/gradle-plugin-c/src/main/java/org/xbib/gradle/plugin/c/toolchain/tools/CCompilerArgsTransformer.java @@ -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 { + + @Override + public List transform(CCompileSpec spec) { + List 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; + } +} diff --git a/gradle-plugin-c/src/main/java/org/xbib/gradle/plugin/c/toolchain/tools/Linker.java b/gradle-plugin-c/src/main/java/org/xbib/gradle/plugin/c/toolchain/tools/Linker.java new file mode 100644 index 0000000..1ec9de8 --- /dev/null +++ b/gradle-plugin-c/src/main/java/org/xbib/gradle/plugin/c/toolchain/tools/Linker.java @@ -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 { + + public Linker(BuildOperationExecutor buildOperationExecutor, + CommandLineToolInvocationWorker commandLineToolInvocationWorker, + CommandLineToolContext invocationContext, + WorkerLeaseService workerLeaseService) { + super(buildOperationExecutor, commandLineToolInvocationWorker, invocationContext, + new LinkerArgsTransformer(), + true, workerLeaseService); + } + + @Override + protected Action> newInvocationAction(final LinkerSpec spec, + List 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 args, File tempDir) { + ArrayList originalArgs = new ArrayList<>(args); + args.clear(); + args.addAll(ArgWriter.argsFileGenerator(new File(tempDir, "options.txt"), + ArgWriter.unixStyleFactory()).transform(originalArgs)); + } +} diff --git a/gradle-plugin-c/src/main/java/org/xbib/gradle/plugin/c/toolchain/tools/LinkerArgsTransformer.java b/gradle-plugin-c/src/main/java/org/xbib/gradle/plugin/c/toolchain/tools/LinkerArgsTransformer.java new file mode 100644 index 0000000..a700dae --- /dev/null +++ b/gradle-plugin-c/src/main/java/org/xbib/gradle/plugin/c/toolchain/tools/LinkerArgsTransformer.java @@ -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 { + + @Override + public List transform(LinkerSpec spec) { + + List 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; + } +} diff --git a/gradle-plugin-c/src/main/java/org/xbib/gradle/plugin/c/toolchain/tools/StaticLibraryArchiver.java b/gradle-plugin-c/src/main/java/org/xbib/gradle/plugin/c/toolchain/tools/StaticLibraryArchiver.java new file mode 100644 index 0000000..5be8208 --- /dev/null +++ b/gradle-plugin-c/src/main/java/org/xbib/gradle/plugin/c/toolchain/tools/StaticLibraryArchiver.java @@ -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 { + + 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> newInvocationAction(final StaticLibraryArchiverSpec spec, + List 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 args, File tempDir) { + // No support for command file + } +} diff --git a/gradle-plugin-c/src/main/java/org/xbib/gradle/plugin/c/toolchain/tools/StaticLibraryArchiverArgsTransformer.java b/gradle-plugin-c/src/main/java/org/xbib/gradle/plugin/c/toolchain/tools/StaticLibraryArchiverArgsTransformer.java new file mode 100644 index 0000000..bc067ef --- /dev/null +++ b/gradle-plugin-c/src/main/java/org/xbib/gradle/plugin/c/toolchain/tools/StaticLibraryArchiverArgsTransformer.java @@ -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 { + + public List transform(StaticLibraryArchiverSpec spec) { + List args = new ArrayList<>(spec.getAllArgs()); + + args.add("-rv"); + + args.add(spec.getOutputFile().getAbsolutePath()); + + for (File file : spec.getObjectFiles()) { + args.add(file.getAbsolutePath()); + } + + return args; + } +} diff --git a/gradle-plugin-c/src/main/java/org/xbib/gradle/plugin/c/toolchain/transform/ExtractZipTransform.java b/gradle-plugin-c/src/main/java/org/xbib/gradle/plugin/c/toolchain/transform/ExtractZipTransform.java new file mode 100644 index 0000000..6a7ac57 --- /dev/null +++ b/gradle-plugin-c/src/main/java/org/xbib/gradle/plugin/c/toolchain/transform/ExtractZipTransform.java @@ -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 { + + @InputArtifact + @PathSensitive(PathSensitivity.RELATIVE) + public abstract Provider 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(); + } +} \ No newline at end of file diff --git a/gradle-plugin-c/src/test/groovy/org/xbib/gradle/plugin/c/test/CPluginIntegrationTest.groovy b/gradle-plugin-c/src/test/groovy/org/xbib/gradle/plugin/c/test/CPluginIntegrationTest.groovy new file mode 100644 index 0000000..1d761af --- /dev/null +++ b/gradle-plugin-c/src/test/groovy/org/xbib/gradle/plugin/c/test/CPluginIntegrationTest.groovy @@ -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()) + } +} diff --git a/gradle-plugin-cmake/NOTICE.txt b/gradle-plugin-cmake/NOTICE.txt new file mode 100644 index 0000000..10d3094 --- /dev/null +++ b/gradle-plugin-cmake/NOTICE.txt @@ -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 diff --git a/gradle-plugin-cmake/README.md b/gradle-plugin-cmake/README.md new file mode 100644 index 0000000..2304e50 --- /dev/null +++ b/gradle-plugin-cmake/README.md @@ -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 +``` diff --git a/gradle-plugin-cmake/build.gradle b/gradle-plugin-cmake/build.gradle index 002fb90..5b56706 100644 --- a/gradle-plugin-cmake/build.gradle +++ b/gradle-plugin-cmake/build.gradle @@ -4,7 +4,7 @@ plugins { 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') dependencies { diff --git a/gradle-plugin-cmake/src/test/groovy/org/xbib/gradle/plugin/cmake/test/CmakePluginIntegrationTest.groovy b/gradle-plugin-cmake/src/test/groovy/org/xbib/gradle/plugin/cmake/test/CmakePluginIntegrationTest.groovy new file mode 100644 index 0000000..aab05c8 --- /dev/null +++ b/gradle-plugin-cmake/src/test/groovy/org/xbib/gradle/plugin/cmake/test/CmakePluginIntegrationTest.groovy @@ -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()) + } +} diff --git a/settings.gradle b/settings.gradle index 7edea19..1d88641 100644 --- a/settings.gradle +++ b/settings.gradle @@ -4,6 +4,7 @@ dependencyResolutionManagement { libs { version('gradle', '8.5') version('groovy', '3.0.17') // MUST match gradle groovy version + // Java 21 version('asm', '9.6') // Attention: it is impossible to develop a gradle plugin with groovy 4! // The gradle plugin publish plugin enforces java-gradle-plugin, @@ -42,6 +43,7 @@ dependencyResolutionManagement { } include 'gradle-plugin-asciidoctor' +include 'gradle-plugin-c' include 'gradle-plugin-cmake' include 'gradle-plugin-docker' include 'gradle-plugin-git'