Compare commits

...

21 Commits
0.0.1 ... main

@ -1,7 +1,9 @@
group = 'org.xbib.gradle.plugin'
wrapper {
gradleVersion = libs.versions.gradle.get()
distributionType = Wrapper.DistributionType.ALL
distributionType = Wrapper.DistributionType.BIN
}
ext {
@ -18,11 +20,3 @@ ext {
licenseName = 'The Apache License, Version 2.0'
licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
}
subprojects {
apply plugin: 'java-library'
apply from: rootProject.file('gradle/ide/idea.gradle')
apply from: rootProject.file('gradle/compile/java.gradle')
apply from: rootProject.file('gradle/test/junit5.gradle')
apply from: rootProject.file('gradle/repositories/maven.gradle')
}

@ -1,47 +1,38 @@
plugins {
id 'java-gradle-plugin'
alias(libs.plugins.publish)
}
apply plugin: 'java-gradle-plugin'
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()
implementation libs.asciidoctorj
implementation libs.jruby
testImplementation libs.spock.core
testImplementation libs.jsoup
}
gradlePlugin {
plugins {
asciidoctorPlugin {
id = 'org.xbib.gradle.plugin.asciidoctor'
implementationClass = 'org.xbib.gradle.plugin.asciidoctor.AsciidoctorPlugin'
}
}
implementation libs.asm
implementation libs.asm.commons
implementation libs.asm.util
testImplementation platform(testLibs.spock.bom)
testImplementation testLibs.spock.core
testImplementation testLibs.jsoup
}
/*
if (project.hasProperty('gradle.publish.key')) {
pluginBundle {
mavenCoordinates {
groupId = "org.xbib.gradle.plugin"
artifactId = "gradle-plugin-asciidoctor"
version = project.version
}
website = 'https://github.com/jprante/gradle-plugins'
vcsUrl = 'https://github.com/jprante/gradle-plugins'
gradlePlugin {
website = 'https://xbib.org/joerg/gradle-plugins/src/branch/main/gradle-plugin-asciidoctor'
vcsUrl = 'https://xbib.org/joerg/gradle-plugins'
plugins {
asciidoctorPlugin {
id = 'org.xbib.gradle.plugin.asciidoctor'
implementationClass = 'org.xbib.gradle.plugin.asciidoctor.AsciidoctorPlugin'
version = project.version
description = 'Asciidoctor plugin for building documentations'
displayName = 'Asciidoctor plugin for building documentations'
tags = ['asciidoctor']
description = 'Asciidoctor plugin for building documentations (inofficial xbib fork)'
displayName = 'Asciidoctor plugin for building documentations (inofficial xbib fork)'
tags.set(['asciidoctor'])
}
}
}
}
*/

@ -1 +1,2 @@
version = 2.5.2.1
name = gradle-plugin-asciidoctor
version = 3.1.0

@ -21,7 +21,7 @@ class AsciidoctorPlugin implements Plugin<Project> {
AsciidoctorExtension extension = project.extensions.create(ASCIIDOCTORJ, AsciidoctorExtension, project)
project.afterEvaluate {
if(project.extensions.asciidoctorj.addDefaultRepositories) {
if (project.extensions.asciidoctorj.addDefaultRepositories) {
project.repositories {
mavenCentral()
}

@ -174,5 +174,7 @@ class AsciidoctorExtensions {
)
new GroovyShell(new Binding(), config)
}
}
}

@ -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.

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

@ -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.

@ -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'])
}
}
}
}

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

@ -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();
}

@ -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");
});
}
}

@ -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());
}
}

@ -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;
}
}

@ -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
}
}

@ -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();
}

@ -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();
}

@ -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;
}
}

@ -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();
}
}

@ -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);
}
}
}

@ -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;
}
}

@ -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));
}
}

@ -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;
}
}

@ -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
}
}

@ -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;
}
}

@ -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();
}
}

@ -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())
}
}

@ -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

@ -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
```

@ -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-cmake'
vcsUrl = 'https://xbib.org/joerg/gradle-plugins'
plugins {
cmakePlugin {
id = 'org.xbib.gradle.plugin.cmake'
implementationClass = 'org.xbib.gradle.plugin.cmake.CMakePlugin'
version = project.version
description = 'Gradle plugin for build with cmake'
displayName = 'Gradle Plugin for build with cmake'
tags.set(['cmake'])
}
}
}
}

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

@ -0,0 +1,100 @@
package org.xbib.gradle.plugin.cmake;
import java.util.ArrayList;
import java.util.List;
import org.gradle.api.DefaultTask;
import org.gradle.api.file.DirectoryProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputDirectory;
import org.gradle.api.tasks.Optional;
import org.gradle.api.tasks.TaskAction;
/**
* Build a configured Build with CMake
*/
public class CMakeBuildTask extends DefaultTask {
private final Property<String> executable;
private final DirectoryProperty workingFolder;
private final Property<String> buildConfig;
private final Property<String> buildTarget;
private final Property<Boolean> buildClean;
public CMakeBuildTask() {
setGroup("cmake");
setDescription("Build a configured Build with CMake");
executable = getProject().getObjects().property(String.class);
workingFolder = getProject().getObjects().directoryProperty();
buildConfig = getProject().getObjects().property(String.class);
buildTarget = getProject().getObjects().property(String.class);
buildClean = getProject().getObjects().property(Boolean.class);
}
public void configureFromProject() {
CMakePluginExtension ext = (CMakePluginExtension) getProject().getExtensions().getByName("cmake");
executable.set(ext.getExecutable());
workingFolder.set(ext.getWorkingFolder());
buildConfig.set(ext.getBuildConfig());
buildTarget.set(ext.getBuildTarget());
buildClean.set(ext.getBuildClean());
}
@Input
@Optional
public Property<String> getExecutable() {
return executable;
}
@InputDirectory
public DirectoryProperty getWorkingFolder() {
return workingFolder;
}
@Input
@Optional
public Property<String> getBuildConfig() {
return buildConfig;
}
@Input
@Optional
public Property<String> getBuildTarget() {
return buildTarget;
}
@Input
@Optional
public Property<Boolean> getBuildClean() {
return buildClean;
}
private List<String> buildCmdLine() {
List<String> parameters = new ArrayList<>();
parameters.add(executable.getOrElse("cmake"));
parameters.add("--build");
parameters.add(".");
if (buildConfig.isPresent()) {
parameters.add("--config");
parameters.add(buildConfig.get());
}
if (buildTarget.isPresent()) {
parameters.add("--target");
parameters.add(buildTarget.get());
}
if (buildClean.getOrElse(Boolean.FALSE)) {
parameters.add("--clean-first");
}
return parameters;
}
@TaskAction
public void build() {
CMakeExecutor executor = new CMakeExecutor(getLogger(), getName());
executor.exec(buildCmdLine(), workingFolder.getAsFile().get());
}
}

@ -0,0 +1,191 @@
package org.xbib.gradle.plugin.cmake;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.gradle.api.DefaultTask;
import org.gradle.api.file.DirectoryProperty;
import org.gradle.api.provider.MapProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputDirectory;
import org.gradle.api.tasks.Optional;
import org.gradle.api.tasks.OutputDirectory;
import org.gradle.api.tasks.TaskAction;
public class CMakeConfigureTask extends DefaultTask {
private final Property<String> executable;
private final DirectoryProperty workingFolder;
private final DirectoryProperty sourceFolder;
private final Property<String> configurationTypes;
private final Property<String> installPrefix;
private final Property<String> generator; // for example: "Visual Studio 16 2019"
private final Property<String> platform; // for example "x64" or "Win32" or "ARM" or "ARM64", supported on vs > 8.0
private final Property<String> toolset; // for example "v142", supported on vs > 10.0
private final Property<Boolean> buildSharedLibs;
private final Property<Boolean> buildStaticLibs;
private final MapProperty<String, String> def;
public CMakeConfigureTask() {
setGroup("cmake");
setDescription("Configure a Build with CMake");
executable = getProject().getObjects().property(String.class);
workingFolder = getProject().getObjects().directoryProperty();
sourceFolder = getProject().getObjects().directoryProperty();
configurationTypes = getProject().getObjects().property(String.class);
installPrefix = getProject().getObjects().property(String.class);
generator = getProject().getObjects().property(String.class);
platform = getProject().getObjects().property(String.class);
toolset = getProject().getObjects().property(String.class);
buildSharedLibs = getProject().getObjects().property(Boolean.class);
buildStaticLibs = getProject().getObjects().property(Boolean.class);
def = getProject().getObjects().mapProperty(String.class, String.class);
// default values
workingFolder.set(new File(getProject().getLayout().getBuildDirectory().get().getAsFile(), "cmake"));
sourceFolder.set(new File(getProject().getLayout().getBuildDirectory().get().getAsFile(), "src" + File.separator + "main" + File.separator + "cpp"));
}
public void configureFromProject() {
CMakePluginExtension ext = (CMakePluginExtension) getProject().getExtensions().getByName("cmake");
executable.set(ext.getExecutable());
workingFolder.set(ext.getWorkingFolder());
sourceFolder.set(ext.getSourceFolder());
configurationTypes.set(ext.getConfigurationTypes());
installPrefix.set(ext.getInstallPrefix());
generator.set(ext.getGenerator());
platform.set(ext.getPlatform());
toolset.set(ext.getToolset());
buildSharedLibs.set(ext.getBuildSharedLibs());
buildStaticLibs.set(ext.getBuildStaticLibs());
def.set(ext.getDefs());
}
/// region getters
@Input
@Optional
public Property<String> getExecutable() {
return executable;
}
@OutputDirectory
public DirectoryProperty getWorkingFolder() {
return workingFolder;
}
@InputDirectory
public DirectoryProperty getSourceFolder() {
return sourceFolder;
}
@Input
@Optional
public Property<String> getConfigurationTypes() {
return configurationTypes;
}
@Input
@Optional
public Property<String> getInstallPrefix() {
return installPrefix;
}
@Input
@Optional
public Property<String> getGenerator() {
return generator;
}
@Input
@Optional
public Property<String> getPlatform() {
return platform;
}
@Input
@Optional
public Property<String> getToolset() {
return toolset;
}
@Input
@Optional
public Property<Boolean> getBuildSharedLibs() {
return buildSharedLibs;
}
@Input
@Optional
public Property<Boolean> getBuildStaticLibs() {
return buildStaticLibs;
}
@Input
@Optional
public MapProperty<String, String> getDef() {
return def;
}
/// endregion
private List<String> buildCmdLine() {
List<String> parameters = new ArrayList<>();
parameters.add(executable.getOrElse("cmake"));
if (generator.isPresent() && !generator.get().isEmpty()) {
parameters.add("-G");
parameters.add(generator.get());
}
if (platform.isPresent() && !platform.get().isEmpty()) {
parameters.add("-A");
parameters.add(platform.get());
}
if (toolset.isPresent() && !toolset.get().isEmpty()) {
parameters.add("-T");
parameters.add(toolset.get());
}
if (configurationTypes.isPresent() && !configurationTypes.get().isEmpty())
parameters.add("-DCMAKE_CONFIGURATION_TYPES=" + configurationTypes.get());
if (installPrefix.isPresent() && !installPrefix.get().isEmpty())
parameters.add("-DCMAKE_INSTALL_PREFIX=" + installPrefix.get());
if (buildSharedLibs.isPresent())
parameters.add("-DBUILD_SHARED_LIBS=" + (buildSharedLibs.get() ? "ON" : "OFF"));
if (buildStaticLibs.isPresent())
parameters.add("-DBUILD_STATIC_LIBS=" + (buildStaticLibs.get() ? "ON" : "OFF"));
if (def.isPresent()) {
for (Map.Entry<String, String> entry : def.get().entrySet())
parameters.add("-D" + entry.getKey() + "=" + entry.getValue());
}
parameters.add(sourceFolder.getAsFile().get().getAbsolutePath());
return parameters;
}
@TaskAction
public void configure() {
CMakeExecutor executor = new CMakeExecutor(getLogger(), getName());
executor.exec(buildCmdLine(), workingFolder.getAsFile().get());
}
}

@ -0,0 +1,83 @@
package org.xbib.gradle.plugin.cmake;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.stream.Stream;
import org.gradle.api.GradleException;
import org.gradle.api.GradleScriptException;
import org.gradle.api.logging.Logger;
public class CMakeExecutor {
private final Logger logger;
private final String taskName;
CMakeExecutor(final Logger logger, final String taskName) {
this.logger = logger;
this.taskName = taskName;
}
protected void exec(final List<String> cmdLine, final File workingFolder) throws GradleException {
// log command line parameters
StringBuilder sb = new StringBuilder(" CMakePlugin.task " + taskName + " - exec: ");
for (String s : cmdLine) {
sb.append(s).append(" ");
}
logger.info(sb.toString());
ProcessBuilder pb = new ProcessBuilder(cmdLine);
pb.directory(workingFolder);
try (ExecutorService executor = Executors.newFixedThreadPool(2)) {
// make sure working folder exists
boolean b = workingFolder.mkdirs();
// start
Process process = pb.start();
Future<Void> stdoutFuture = executor.submit(() -> {
readStream(process.getInputStream(), true);
return null;
});
Future<Void> stderrFuture = executor.submit(() -> {
readStream(process.getErrorStream(), false);
return null;
});
int retCode = process.waitFor();
warnIfTimeout(stdoutFuture,
"CMakeExecutor[" + taskName + "]Warn: timed out waiting for stdout to be closed.");
warnIfTimeout(stderrFuture,
"CMakeExecutor[" + taskName + "]Warn: timed out waiting for stderr to be closed.");
if (retCode != 0) {
throw new GradleException("[" + taskName + "]Error: CMAKE returned " + retCode);
}
} catch (IOException | InterruptedException | ExecutionException e) {
throw new GradleScriptException("CMakeExecutor[" + taskName + "].", e);
}
}
private void readStream(final InputStream inputStream, boolean isStdOut) {
final Stream<String> lines = new BufferedReader(new InputStreamReader(inputStream)).lines();
if (isStdOut) {
lines.forEach(logger::info);
} else {
lines.forEach(logger::error);
}
}
private void warnIfTimeout(final Future<Void> future, final String message)
throws ExecutionException, InterruptedException {
try {
future.get(3, TimeUnit.SECONDS);
} catch (TimeoutException e) {
logger.warn(message);
}
}
}

@ -0,0 +1,141 @@
package org.xbib.gradle.plugin.cmake;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import org.gradle.api.GradleException;
import org.gradle.api.GradleScriptException;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.api.logging.LogLevel;
import org.gradle.api.tasks.TaskContainer;
public class CMakePlugin implements Plugin<Project> {
private static final String CMAKE_CONFIGURE = "cmakeConfigure";
private static final String CMAKE_BUILD = "cmakeBuild";
private boolean deleteDirectory(File directoryToBeDeleted) {
File[] allContents = directoryToBeDeleted.listFiles();
if (allContents != null) {
for (File file : allContents) {
deleteDirectory(file);
}
}
return directoryToBeDeleted.delete();
}
@Override
public void apply(Project project) {
project.getPlugins().apply("base");
final CMakePluginExtension extension = project.getExtensions()
.create("cmake", CMakePluginExtension.class, project);
final Task cmakeClean = project.task("cmakeClean").doFirst(task -> {
File workingFolder = extension.getWorkingFolder().getAsFile().get().getAbsoluteFile();
if (workingFolder.exists()) {
project.getLogger().info("Deleting folder " + workingFolder);
if (!deleteDirectory(workingFolder)) {
throw new GradleException("Could not delete working folder " + workingFolder);
}
}
});
cmakeClean.setGroup("cmake");
cmakeClean.setDescription("Clean CMake configuration");
final Task cmakeGenerators = project.task("cmakeGenerators").doFirst(task -> {
ProcessBuilder pb = new ProcessBuilder(extension.getExecutable().getOrElse("cmake"),
"--help");
try {
// start
Process process = pb.start();
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
boolean foundGenerators = false;
while ((line = reader.readLine()) != null) {
if (line.equals("Generators")) {
foundGenerators = true;
}
if (foundGenerators) {
project.getLogger().log(LogLevel.QUIET, line);
}
}
process.waitFor();
} catch (IOException | InterruptedException e) {
throw new GradleScriptException("cmake --help failed.", e);
}
});
cmakeGenerators.setGroup("cmake");
cmakeGenerators.setDescription("List available CMake generators");
project.afterEvaluate(p -> {
final TaskContainer tasks = project.getTasks();
if (extension.getTargets().getTargetContainer().isEmpty()) {
p.getTasks().register(CMAKE_CONFIGURE, CMakeConfigureTask.class, task -> {
task.getExecutable().set(extension.getExecutable());
task.getWorkingFolder().set(extension.getWorkingFolder());
task.getSourceFolder().set(extension.getSourceFolder());
task.getConfigurationTypes().set(extension.getConfigurationTypes());
task.getInstallPrefix().set(extension.getInstallPrefix());
task.getGenerator().set(extension.getGenerator());
task.getPlatform().set(extension.getPlatform());
task.getToolset().set(extension.getToolset());
task.getBuildSharedLibs().set(extension.getBuildSharedLibs());
task.getBuildStaticLibs().set(extension.getBuildStaticLibs());
task.getDef().set(extension.getDefs().isPresent() ? extension.getDefs() : extension.getDef());
});
p.getTasks().register(CMAKE_BUILD, CMakeBuildTask.class, task -> {
task.getExecutable().set(extension.getExecutable());
task.getWorkingFolder().set(extension.getWorkingFolder());
task.getBuildConfig().set(extension.getBuildConfig());
task.getBuildTarget().set(extension.getBuildTarget());
task.getBuildClean().set(extension.getBuildClean());
});
} else {
extension.getTargets().getTargetContainer().getAsMap()
.forEach((name, target) -> {
tasks.register(CMAKE_CONFIGURE + name, CMakeConfigureTask.class, task -> {
task.configureFromProject();
if (target.getExecutable().isPresent())
task.getExecutable().set(target.getExecutable());
if (target.getWorkingFolder().isPresent())
task.getWorkingFolder().set(target.getWorkingFolder());
if (target.getSourceFolder().isPresent())
task.getSourceFolder().set(target.getSourceFolder());
if (target.getConfigurationTypes().isPresent())
task.getConfigurationTypes().set(target.getConfigurationTypes());
if (target.getInstallPrefix().isPresent())
task.getInstallPrefix().set(target.getInstallPrefix());
if (target.getGenerator().isPresent()) task.getGenerator().set(target.getGenerator());
if (target.getPlatform().isPresent()) task.getPlatform().set(target.getPlatform());
if (target.getToolset().isPresent()) task.getToolset().set(target.getToolset());
if (target.getBuildSharedLibs().isPresent())
task.getBuildSharedLibs().set(target.getBuildSharedLibs());
if (target.getBuildStaticLibs().isPresent())
task.getBuildStaticLibs().set(target.getBuildStaticLibs());
if (target.getDefs().isPresent()) task.getDef().set(target.getDefs());
});
tasks.register(CMAKE_BUILD + name, CMakeBuildTask.class, task -> {
task.configureFromProject();
if (target.getExecutable().isPresent())
task.getExecutable().set(target.getExecutable());
if (target.getWorkingFolder().isPresent())
task.getWorkingFolder().set(target.getWorkingFolder());
if (target.getBuildConfig().isPresent())
task.getBuildConfig().set(target.getBuildConfig());
if (target.getBuildTarget().isPresent())
task.getBuildTarget().set(target.getBuildTarget());
if (target.getBuildClean().isPresent())
task.getBuildClean().set(target.getBuildClean());
});
});
}
tasks.withType(CMakeBuildTask.class)
.forEach(task -> task.dependsOn(tasks.withType(CMakeConfigureTask.class)));
p.getTasks().named("clean").configure(task -> task.dependsOn("cmakeClean"));
p.getTasks().named("build").configure(task -> task.dependsOn(tasks.withType(
CMakeBuildTask.class)));
});
}
}

@ -0,0 +1,121 @@
package org.xbib.gradle.plugin.cmake;
import java.io.File;
import org.gradle.api.Project;
import org.gradle.api.file.DirectoryProperty;
import org.gradle.api.provider.MapProperty;
import org.gradle.api.provider.Property;
public class CMakePluginExtension {
// parameters used by config and build step
private final Property<String> executable;
private final DirectoryProperty workingFolder;
// parameters used by config step
private final DirectoryProperty sourceFolder;
private final Property<String> configurationTypes;
private final Property<String> installPrefix;
private final Property<String> generator; // for example: "Visual Studio 16 2019"
private final Property<String> platform; // for example "x64" or "Win32" or "ARM" or "ARM64", supported on vs > 8.0
private final Property<String> toolset; // for example "v142", supported on vs > 10.0
private final Property<Boolean> buildSharedLibs;
private final Property<Boolean> buildStaticLibs;
private final MapProperty<String, String> defs;
private final MapProperty<String, String> def;
// parameters used on build step
private final Property<String> buildConfig;
private final Property<String> buildTarget;
private final Property<Boolean> buildClean;
private final TargetListExtension targets;
private final Project project;
public CMakePluginExtension(Project project) {
executable = project.getObjects().property(String.class);
workingFolder = project.getObjects().directoryProperty();
sourceFolder = project.getObjects().directoryProperty();
configurationTypes = project.getObjects().property(String.class);
installPrefix = project.getObjects().property(String.class);
generator = project.getObjects().property(String.class);
platform = project.getObjects().property(String.class);
toolset = project.getObjects().property(String.class);
buildSharedLibs = project.getObjects().property(Boolean.class);
buildStaticLibs = project.getObjects().property(Boolean.class);
defs = project.getObjects().mapProperty(String.class, String.class);
def = project.getObjects().mapProperty(String.class, String.class); // for backwards compat
buildConfig = project.getObjects().property(String.class);
buildTarget = project.getObjects().property(String.class);
buildClean = project.getObjects().property(Boolean.class);
this.targets = project.getObjects().newInstance(TargetListExtension.class, project);
// default values
workingFolder.set(new File(project.getLayout().getBuildDirectory().get().getAsFile(), "cmake"));
sourceFolder.set(new File(project.getLayout().getBuildDirectory().get().getAsFile(), "src" + File.separator + "main" + File.separator + "cpp"));
this.project = project;
}
public Property<String> getExecutable() {
return executable;
}
public DirectoryProperty getWorkingFolder() {
return workingFolder;
}
public DirectoryProperty getSourceFolder() {
return sourceFolder;
}
public Property<String> getConfigurationTypes() {
return configurationTypes;
}
public Property<String> getInstallPrefix() {
return installPrefix;
}
public Property<String> getGenerator() {
return generator;
}
public Property<String> getPlatform() {
return platform;
}
public Property<String> getToolset() {
return toolset;
}
public Property<Boolean> getBuildSharedLibs() {
return buildSharedLibs;
}
public Property<Boolean> getBuildStaticLibs() {
return buildStaticLibs;
}
public MapProperty<String, String> getDef() {
return def;
}
public MapProperty<String, String> getDefs() {
return defs;
}
public Property<String> getBuildConfig() {
return buildConfig;
}
public Property<String> getBuildTarget() {
return buildTarget;
}
public Property<Boolean> getBuildClean() {
return buildClean;
}
public TargetListExtension getTargets() {
return targets;
}
}

@ -0,0 +1,174 @@
package org.xbib.gradle.plugin.cmake;
import java.io.File;
import java.util.Map;
import org.gradle.api.Project;
import org.gradle.api.file.DirectoryProperty;
import org.gradle.api.provider.MapProperty;
import org.gradle.api.provider.Property;
public class TargetExtension {
// parameters used by config and build step
private final Property<String> executable;
private final DirectoryProperty workingFolder;
// parameters used by config step
private final DirectoryProperty sourceFolder;
private final Property<String> configurationTypes;
private final Property<String> installPrefix;
private final Property<String> generator; // for example: "Visual Studio 16 2019"
private final Property<String> platform; // for example "x64" or "Win32" or "ARM" or "ARM64", supported on vs > 8.0
private final Property<String> toolset; // for example "v142", supported on vs > 10.0
private final Property<Boolean> buildSharedLibs;
private final Property<Boolean> buildStaticLibs;
private final MapProperty<String, String> defs;
// parameters used on build step
private final Property<String> buildConfig;
private final Property<String> buildTarget;
private final Property<Boolean> buildClean;
private final String name;
public TargetExtension(final Project project, final String name) {
executable = project.getObjects().property(String.class);
workingFolder = project.getObjects().directoryProperty();
sourceFolder = project.getObjects().directoryProperty();
configurationTypes = project.getObjects().property(String.class);
installPrefix = project.getObjects().property(String.class);
generator = project.getObjects().property(String.class);
platform = project.getObjects().property(String.class);
toolset = project.getObjects().property(String.class);
buildSharedLibs = project.getObjects().property(Boolean.class);
buildStaticLibs = project.getObjects().property(Boolean.class);
defs = project.getObjects().mapProperty(String.class, String.class);
buildConfig = project.getObjects().property(String.class);
buildTarget = project.getObjects().property(String.class);
buildClean = project.getObjects().property(Boolean.class);
this.name = name;
}
public Property<String> getExecutable() {
return executable;
}
public DirectoryProperty getWorkingFolder() {
return workingFolder;
}
public DirectoryProperty getSourceFolder() {
return sourceFolder;
}
public Property<String> getConfigurationTypes() {
return configurationTypes;
}
public Property<String> getInstallPrefix() {
return installPrefix;
}
public Property<String> getGenerator() {
return generator;
}
public Property<String> getPlatform() {
return platform;
}
public Property<String> getToolset() {
return toolset;
}
public Property<Boolean> getBuildSharedLibs() {
return buildSharedLibs;
}
public Property<Boolean> getBuildStaticLibs() {
return buildStaticLibs;
}
public MapProperty<String, String> getDefs() {
return defs;
}
public Property<String> getBuildConfig() {
return buildConfig;
}
public Property<String> getBuildTarget() {
return buildTarget;
}
public Property<Boolean> getBuildClean() {
return buildClean;
}
public String getName() {
return name;
}
public void setExecutable(String executable) {
this.executable.set(executable);
}
public void setWorkingFolder(File workingFolder) {
this.workingFolder.set(workingFolder);
}
public void setWorkingFolder(String workingFolder) {
this.workingFolder.set(new File(workingFolder));
}
public void setSourceFolder(File sourceFolder) {
this.sourceFolder.set(sourceFolder);
}
public void setSourceFolder(String sourceFolder) {
this.sourceFolder.set(new File(sourceFolder));
}
public void setConfigurationTypes(String configurationTypes) {
this.configurationTypes.set(configurationTypes);
}
public void setInstallPrefix(String installPrefix) {
this.installPrefix.set(installPrefix);
}
public void setGenerator(String generator) {
this.generator.set(generator);
}
public void setPlatform(String platform) {
this.platform.set(platform);
}
public void setToolset(String toolset) {
this.toolset.set(toolset);
}
public void setBuildSharedLibs(Boolean buildSharedLibs) {
this.buildSharedLibs.set(buildSharedLibs);
}
public void setBuildStaticLibs(Boolean buildStaticLibs) {
this.buildStaticLibs.set(buildStaticLibs);
}
public void setDefs(Map<String, String> defs) {
this.defs.set(defs);
}
public void setBuildConfig(String buildConfig) {
this.buildConfig.set(buildConfig);
}
public void setBuildTarget(String buildTarget) {
this.buildTarget.set(buildTarget);
}
public void setBuildClean(Boolean buildClean) {
this.buildClean.set(buildClean);
}
}

@ -0,0 +1,31 @@
package org.xbib.gradle.plugin.cmake;
import groovy.lang.Closure;
import groovy.lang.MissingMethodException;
import javax.inject.Inject;
import org.gradle.api.NamedDomainObjectContainer;
import org.gradle.api.Project;
public class TargetListExtension {
@Inject
public TargetListExtension(Project project) {
targetContainer = project.container(TargetExtension.class, name -> new TargetExtension(project, name));
}
private final NamedDomainObjectContainer<TargetExtension> targetContainer;
public NamedDomainObjectContainer<TargetExtension> getTargetContainer() {
return targetContainer;
}
public Object methodMissing(String name, Object args) {
if (args instanceof Object[] && ((Object[]) args)[0] instanceof Closure<?> closure) {
return targetContainer.create(name, closure);
} else {
final Object[] normalizedArgs;
normalizedArgs = args instanceof Object[] ? (Object[]) args : new Object[]{args};
throw new MissingMethodException(name, this.getClass(), normalizedArgs);
}
}
}

@ -0,0 +1,56 @@
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.condition.DisabledOnOs
import org.junit.jupiter.api.condition.OS
import org.junit.jupiter.api.io.TempDir
import static org.junit.jupiter.api.Assertions.assertEquals
@DisabledOnOs(OS.MAC)
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())
}
}

@ -1,44 +1,30 @@
plugins {
id 'java-gradle-plugin'
alias(libs.plugins.publish)
}
apply plugin: 'java-gradle-plugin'
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()
}
gradlePlugin {
plugins {
dockerPlugin {
id = 'org.xbib.gradle.plugin.docker'
implementationClass = 'org.xbib.gradle.plugin.docker.DockerPlugin'
}
}
}
if (project.hasProperty('gradle.publish.key')) {
pluginBundle {
mavenCoordinates {
groupId = "org.xbib.gradle.plugin"
artifactId = "gradle-plugin-docker"
version = project.version
}
website = 'https://github.com/jprante/gradle-plugins'
vcsUrl = 'https://github.com/jprante/gradle-plugins'
plugins {
dockerPlugin {
id = 'org.xbib.gradle.plugin.docker'
version = project.version
description = 'Docker plugin for build and push by Dockerfile'
displayName = 'Docker Plugin for build and push by Dockerfile'
tags = ['docker']
gradlePlugin {
website = 'https://xbib.org/joerg/gradle-plugins/src/branch/main/gradle-plugin-docker'
vcsUrl = 'https://xbib.org/joerg/gradle-plugins'
plugins {
dockerPlugin {
id = 'org.xbib.gradle.plugin.docker'
implementationClass = 'org.xbib.gradle.plugin.docker.DockerPlugin'
version = project.version
description = 'Docker plugin for build and push by Dockerfile'
displayName = 'Docker Plugin for build and push by Dockerfile'
tags.set(['docker'])
}
}
}
}
}

@ -1 +1,2 @@
version = 2.4.0
name = gradle-plugin-docker
version = 3.1.0

@ -5,10 +5,13 @@ 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.condition.DisabledOnOs
import org.junit.jupiter.api.condition.OS
import org.junit.jupiter.api.io.TempDir
import static org.junit.jupiter.api.Assertions.assertEquals
@DisabledOnOs(OS.MAC)
class DockerPluginIntegrationTest {
private File projectDir

@ -7,40 +7,28 @@ apply plugin: 'java-gradle-plugin'
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()
api libs.groovy.git
testImplementation gradleTestKit()
testImplementation libs.spock.core
testImplementation libs.junit4
}
gradlePlugin {
plugins {
gitPlugin {
id = 'org.xbib.gradle.plugin.git'
implementationClass = 'org.xbib.gradle.plugin.git.GitPlugin'
}
}
testImplementation testLibs.spock.core
testImplementation testLibs.junit4
}
if (project.hasProperty('gradle.publish.key')) {
pluginBundle {
mavenCoordinates {
groupId = "org.xbib.gradle.plugin"
artifactId = "gradle-plugin-git"
version = project.version
}
website = 'https://github.com/jprante/gradle-plugins'
vcsUrl = 'https://github.com/jprante/gradle-plugins'
gradlePlugin {
website = 'https://xbib.org/joerg/gradle-plugins/src/branch/main/gradle-plugin-git'
vcsUrl = 'https://xbib.org/joerg/gradle-plugins'
plugins {
gitPlugin {
id = 'org.xbib.gradle.plugin.git'
implementationClass = 'org.xbib.gradle.plugin.git.GitPlugin'
version = project.version
description = 'Git client plugin based on JGit'
displayName = 'Git client plugin based on JGit'
tags = ['git']
tags.set(['git'])
}
}
}

@ -1 +1,2 @@
version = 2.2.0
name = gradle-plugin-docker
version = 3.1.0

@ -0,0 +1,34 @@
plugins {
id 'java-gradle-plugin'
alias(libs.plugins.publish)
}
apply plugin: 'java-gradle-plugin'
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()
implementation libs.jsoup
implementation libs.javapoet
testImplementation gradleTestKit()
}
if (project.hasProperty('gradle.publish.key')) {
gradlePlugin {
website = 'https://xbib.org/joerg/gradle-plugins/src/branch/main/gradle-plugin-j2html'
vcsUrl = 'https://xbib.org/joerg/gradle-plugins'
plugins {
j2htmlPlugin {
id = 'org.xbib.gradle.plugin.j2html'
implementationClass = 'org.xbib.gradle.plugin.j2html.J2HtmlPlugin'
version = project.version
description = 'Gradle J2HTML plugin'
displayName = 'Gradle J2HTML plugin'
tags.set(['j2html'])
}
}
}
}

@ -0,0 +1,33 @@
package com.j2html.codegen;
import com.j2html.codegen.generators.SpecializedTagClassCodeGenerator;
import com.j2html.codegen.generators.AttributeInterfaceCodeGenerator;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
public final class App
{
public static void main( String[] args )
{
final Path relPath = Paths.get("../library/src/main/java/j2html/");
final Path absPath = relPath.toAbsolutePath();
System.out.println("writing in "+absPath);
//decide if the files should be
//deleted or generated
final boolean delete = false;
try {
AttributeInterfaceCodeGenerator.generate(absPath, delete);
SpecializedTagClassCodeGenerator.generate(absPath, delete);
//TagCreatorCodeGenerator.print();
} catch (IOException e) {
e.printStackTrace();
}
//don't forget to auto-reformat the generated code.
}
}

@ -0,0 +1,50 @@
package com.j2html.codegen;
import com.j2html.codegen.model.AttrD;
import static com.j2html.codegen.generators.TagCreatorCodeGenerator.containerTags;
import static com.j2html.codegen.generators.TagCreatorCodeGenerator.emptyTags;
import static com.j2html.codegen.model.AttributesList.attributesDescriptive;
import static com.j2html.codegen.model.AttributesList.getCustomAttributesForHtmlTag;
import static java.lang.System.*;
public class Export {
public static void main(String[] args){
for (final String tag : emptyTags()) {
out.print("EMPTY-ELEMENT[");
out.print(tag);
out.print("]");
out.println();
}
for (final String tag : containerTags()) {
out.print("ELEMENT[");
out.print(tag);
out.print("]");
out.println();
}
out.println();
for(AttrD attr : attributesDescriptive()){
if(attr.hasArgument){
out.print("STRING");
}else{
out.print("BOOLEAN");
}
out.print("[");
out.print(attr.attr);
out.print("]");
out.println();
for(String tag : attr.tags){
out.print("ATTRIBUTE[");
out.print(tag);
out.print(":");
out.print(attr.attr);
out.print("]");
out.println();
}
out.println();
}
}
}

@ -0,0 +1,199 @@
package com.j2html.codegen;
import com.j2html.codegen.Model.Node;
import com.squareup.javapoet.*;
import javax.lang.model.element.Modifier;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import static com.j2html.codegen.Model.Metadata.ON_OFF;
import static com.j2html.codegen.Model.Metadata.SELF_CLOSING;
public class Generator {
public static final ClassName INSTANCE = ClassName.get("j2html.tags", "IInstance");
public static final ClassName TAG = ClassName.get("j2html.tags", "Tag");
public static final ClassName EMPTY_TAG = ClassName.get("j2html.tags", "EmptyTag");
public static final ClassName CONTAINER_TAG = ClassName.get("j2html.tags", "ContainerTag");
public static void main(String... args) throws IOException {
Path path = Paths.get("j2html-codegen", "src", "test", "resources", "html.model");
String definitions = new String(Files.readAllBytes(path));
Model model = new Model();
Parser.parse(definitions, model);
Path dir = Paths.get("/j2html/generated-source");
Files.createDirectories(dir);
generate(dir, "j2html.tags.attributes", "j2html.tags.specialized", model);
}
public static void generate(Path root, String attributePkg, String elementPkg, Model model) throws IOException {
Map<String, JavaFile> attributes = generateAttributePackage(attributePkg, model);
for (JavaFile file : attributes.values()) {
file.writeTo(root);
}
Map<String, JavaFile> elements = generateElementPackage(elementPkg, model, attributes);
for (JavaFile file : elements.values()) {
file.writeTo(root);
}
}
private static Map<String, JavaFile> generateElementPackage(String pkg, Model model, Map<String, JavaFile> attributes) {
Map<String, JavaFile> files = new HashMap<>();
// Convert all elements into classes.
for (Node element : model.elements()) {
ClassName className = ClassName.get(pkg, capitalize(element.name) + "Tag");
TypeSpec.Builder type = defineElementClass(element, className);
// Assign attributes to this element.
for (Node attribute : element.children) {
JavaFile file = attributes.get(attribute.name);
type.addSuperinterface(
ParameterizedTypeName.get(
ClassName.get(file.packageName, file.typeSpec.name),
className
)
);
}
files.put(
element.name,
JavaFile.builder(pkg, type.build())
.skipJavaLangImports(true)
.build()
);
}
return files;
}
private static Map<String, JavaFile> generateAttributePackage(String pkg, Model model) {
Map<String, JavaFile> files = new HashMap<>();
// Convert all attributes into classes.
for (Node attribute : model.attributes()) {
TypeSpec.Builder type = defineAttributeClass(pkg, attribute);
if (attribute.type.equals(Node.Type.STRING)) {
defineStringAttributeMethods(attribute, type);
} else if (attribute.type.equals(Node.Type.BOOLEAN) && !attribute.is(ON_OFF)) {
defineBooleanAttributeMethods(attribute, type);
} else if (attribute.type.equals(Node.Type.BOOLEAN) && attribute.is(ON_OFF)) {
defineOnOffAttributeMethods(attribute, type);
}
files.put(
attribute.name,
JavaFile.builder(pkg, type.build())
.skipJavaLangImports(true)
.build()
);
}
return files;
}
private static TypeSpec.Builder defineElementClass(Node element, ClassName className) {
MethodSpec constructor = MethodSpec.constructorBuilder()
.addModifiers(Modifier.PUBLIC)
.addStatement("super(\"" + element.name + "\")")
.build();
TypeSpec.Builder type = TypeSpec.classBuilder(className)
.addModifiers(Modifier.PUBLIC)
.superclass(
ParameterizedTypeName.get(element.is(SELF_CLOSING) ? EMPTY_TAG : CONTAINER_TAG, className)
)
.addMethod(constructor);
return type;
}
private static TypeSpec.Builder defineAttributeClass(String pkg, Node attribute) {
ClassName name = ClassName.get(pkg, "I" + capitalize(attribute.name));
return TypeSpec.interfaceBuilder(name)
.addSuperinterface(ParameterizedTypeName.get(INSTANCE, TypeVariableName.get("T")))
.addTypeVariable(TypeVariableName.get("T", ParameterizedTypeName.get(TAG, TypeVariableName.get("T"))))
.addModifiers(Modifier.PUBLIC);
}
private static void defineBooleanAttributeMethods(Node attribute, TypeSpec.Builder type) {
MethodSpec with = MethodSpec.methodBuilder(methodName("is", attribute.name))
.addModifiers(Modifier.PUBLIC, Modifier.DEFAULT)
.addStatement("return self().attr(\"" + attribute.name + "\")")
.returns(TypeVariableName.get("T"))
.build();
MethodSpec withCond = MethodSpec.methodBuilder(methodName("withCond", attribute.name))
.addModifiers(Modifier.PUBLIC, Modifier.DEFAULT)
.addParameter(TypeName.BOOLEAN, "enable", Modifier.FINAL)
.addStatement("return enable ? self().attr(\"" + attribute.name + "\") : self()")
.returns(TypeVariableName.get("T"))
.build();
type.addMethod(with);
type.addMethod(withCond);
}
private static void defineOnOffAttributeMethods(Node attribute, TypeSpec.Builder type) {
MethodSpec with = MethodSpec.methodBuilder(methodName("is", attribute.name))
.addModifiers(Modifier.PUBLIC, Modifier.DEFAULT)
.addStatement("return self().attr(\"" + attribute.name + "\", \"on\")")
.returns(TypeVariableName.get("T"))
.build();
MethodSpec withCond = MethodSpec.methodBuilder(methodName("withCond", attribute.name))
.addModifiers(Modifier.PUBLIC, Modifier.DEFAULT)
.addParameter(TypeName.BOOLEAN, "enable", Modifier.FINAL)
.addStatement("return enable ? self().attr(\"" + attribute.name + "\", \"on\") : self()")
.returns(TypeVariableName.get("T"))
.build();
type.addMethod(with);
type.addMethod(withCond);
}
private static void defineStringAttributeMethods(Node attribute, TypeSpec.Builder type) {
MethodSpec with = MethodSpec.methodBuilder(methodName("with", attribute.name))
.addModifiers(Modifier.PUBLIC, Modifier.DEFAULT)
.addParameter(String.class, parameter(attribute), Modifier.FINAL)
.addStatement("return self().attr(\"" + attribute.name + "\", " + parameter(attribute) + ")")
.returns(TypeVariableName.get("T"))
.build();
MethodSpec withCond = MethodSpec.methodBuilder(methodName("withCond", attribute.name))
.addModifiers(Modifier.PUBLIC, Modifier.DEFAULT)
.addParameter(TypeName.BOOLEAN, "enable", Modifier.FINAL)
.addParameter(String.class, parameter(attribute), Modifier.FINAL)
.addStatement("return enable ? self().attr(\"" + attribute.name + "\", " + parameter(attribute) + ") : self()")
.returns(TypeVariableName.get("T"))
.build();
type.addMethod(with);
type.addMethod(withCond);
}
private static String parameter(Node attribute) {
return attribute.name + "_";
}
private static String methodName(String... words) {
String[] camelCase = new String[words.length];
camelCase[0] = words[0];
for (int i = 1; i < words.length; i++) {
camelCase[i] = capitalize(words[i]);
}
return String.join("", camelCase);
}
private static String capitalize(String word) {
return word.substring(0, 1).toUpperCase() + word.substring(1).toLowerCase();
}
}

@ -0,0 +1,16 @@
package com.j2html.codegen;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
public final class GeneratorUtil {
public static final void deleteAllFilesInDir(final Path dir) throws IOException {
for(final File file : dir.toFile().listFiles()){
System.out.println("deleting " + file.toPath());
Files.delete(file.toPath());
}
}
}

@ -0,0 +1,171 @@
package com.j2html.codegen;
import java.util.*;
import static com.j2html.codegen.Model.Metadata.ON_OFF;
import static com.j2html.codegen.Model.Metadata.SELF_CLOSING;
import static com.j2html.codegen.Model.Node.Type.*;
public class Model implements Parser.Listener {
private Map<String, Node> elements;
private Map<String, Node> attributes;
public Model() {
elements = new LinkedHashMap<>();
attributes = new LinkedHashMap<>();
}
public Collection<Node> elements(){
return elements.values();
}
public Collection<Node> attributes(){
return attributes.values();
}
public Node addElement(String name) {
return add(ELEMENT, name, elements);
}
public Node addBooleanAttribute(String name) {
return add(BOOLEAN, name, attributes);
}
public Node addStringAttribute(String name) {
return add(STRING, name, attributes);
}
public Node element(String name) {
if (!elements.containsKey(name)) {
throw new NodeDoesNotExist(name);
}
return elements.get(name);
}
public Node attribute(String name) {
if (!attributes.containsKey(name)) {
throw new NodeDoesNotExist(name);
}
return attributes.get(name);
}
private Node add(Node.Type type, String name, Map<String, Node> nodes) {
if (nodes.containsKey(name)) {
throw new NodeAlreadyExists(name);
}
Node node = new Node(type, name);
nodes.put(name, node);
return node;
}
@Override
public void lineCommented(int line, String txt) {
// Ignore.
}
@Override
public void elementDefined(int line, String name) {
attempt(() -> addElement(name), line);
}
@Override
public void emptyElementDefined(int line, String name) {
attempt(() -> addElement(name).annotate(SELF_CLOSING), line);
}
@Override
public void booleanDefined(int line, String name) {
attempt(() -> addBooleanAttribute(name), line);
}
@Override
public void onOffDefined(int line, String name) {
attempt(() -> addBooleanAttribute(name).annotate(ON_OFF), line);
}
@Override
public void stringDefined(int line, String name) {
attempt(() -> addStringAttribute(name), line);
}
@Override
public void attributeDefined(int line, String element, String name) {
attempt(() -> element(element).addChild(attribute(name)), line);
}
@Override
public void invalidLine(int line, String txt) {
throw new RuntimeException("Invalid line [" + line + "]: " + txt);
}
@FunctionalInterface
private interface Unsafe {
void call() throws RuntimeException;
}
private void attempt(Unsafe operation, int line) {
try {
operation.call();
} catch (RuntimeException e) {
throw new InvalidModel(e, line);
}
}
public static class Node {
enum Type {
ELEMENT,
BOOLEAN,
STRING
}
public final Type type;
public final String name;
public final List<Metadata> metadata;
public final List<Node> children;
private Node(Type type, String name) {
this.type = type;
this.name = name;
this.metadata = new ArrayList<>();
this.children = new ArrayList<>();
}
public void annotate(Metadata meta) {
metadata.add(meta);
}
public void addChild(Node node) {
children.add(node);
}
public boolean is(Metadata annotation){
return metadata.contains(annotation);
}
}
public enum Metadata {
SELF_CLOSING,
ON_OFF,
OBSOLETE
}
public static class InvalidModel extends RuntimeException {
public InvalidModel(Exception cause, int line) {
super(cause.getMessage() + ". At line " + line, cause);
}
}
public static class NodeAlreadyExists extends RuntimeException {
public NodeAlreadyExists(String name) {
super("Node already exists: " + name);
}
}
public static class NodeDoesNotExist extends RuntimeException {
public NodeDoesNotExist(String name) {
super("Node does not exist: " + name);
}
}
}

@ -0,0 +1,91 @@
package com.j2html.codegen;
import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Parser {
private static final Pattern EMPTY_LINE_PATTERN = Pattern.compile("\\s*");
private static final Pattern COMMENT_PATTERN = Pattern.compile("#.*");
private static final Pattern NODE_PATTERN = Pattern.compile("(?<type>ELEMENT|EMPTY-ELEMENT|BOOLEAN|ONOFF|STRING)\\[(?<name>\\S+)\\]");
private static final Pattern ATTRIBUTE_PATTERN = Pattern.compile("ATTRIBUTE\\[(?<element>\\S+):(?<name>\\S+)\\]");
public interface Listener {
void lineCommented(int line, String txt);
void elementDefined(int line, String name);
void emptyElementDefined(int line, String name);
void booleanDefined(int line, String name);
void onOffDefined(int line, String name);
void stringDefined(int line, String name);
void attributeDefined(int line, String element, String name);
void invalidLine(int line, String txt);
}
public static void parse(String txt, Listener listener) {
String[] lines = txt.split("[\r\n]+");
for (int i = 0; i < lines.length; i++) {
int number = i + 1;
String line = lines[i];
if (match(EMPTY_LINE_PATTERN, line)) continue;
if (match(COMMENT_PATTERN, line, matcher -> {
listener.lineCommented(number, line);
})) continue;
if (match(NODE_PATTERN, line, matcher -> {
String type = matcher.group("type");
String name = matcher.group("name");
switch (type) {
case "ELEMENT":
listener.elementDefined(number, name);
break;
case "EMPTY-ELEMENT":
listener.emptyElementDefined(number, name);
break;
case "BOOLEAN":
listener.booleanDefined(number, name);
break;
case "ONOFF":
listener.onOffDefined(number, name);
break;
case "STRING":
listener.stringDefined(number, name);
break;
}
})) continue;
if (match(ATTRIBUTE_PATTERN, line, matcher -> {
listener.attributeDefined(
number,
matcher.group("element"),
matcher.group("name")
);
})) continue;
listener.invalidLine(number, line);
}
}
private static boolean match(Pattern pattern, String txt) {
return pattern.matcher(txt).matches();
}
private static boolean match(Pattern pattern, String txt, Consumer<Matcher> onMatch) {
Matcher matcher = pattern.matcher(txt);
if (matcher.matches()) {
onMatch.accept(matcher);
return true;
}
return false;
}
}

@ -0,0 +1,185 @@
package com.j2html.codegen.generators;
import com.j2html.codegen.GeneratorUtil;
import com.j2html.codegen.model.AttrD;
import com.j2html.codegen.model.AttributesList;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
public final class AttributeInterfaceCodeGenerator {
private static final String relPath = "tags/attributes/";
public static void generate(final Path absPath, final boolean delete) throws IOException {
//delete all files in the directory for fresh generation
final Path dir = Paths.get(absPath.toString(),relPath);
GeneratorUtil.deleteAllFilesInDir(dir);
for (final AttrD attr : AttributesList.attributesDescriptive()) {
final Path path = makePath(attr.attr, absPath);
final String interfaceName = interfaceNameFromAttribute(attr.attr)+"<T extends Tag<T>>";
/*
IFormAction<T extends Tag<T>> extends IInstance<T>
default T withFormAction(String formAction){
return self().attr("formaction", formAction);
}
*/
final String interfaceStr = getInterfaceTemplate(
interfaceName,
Optional.of("IInstance<T>"),
Arrays.asList("j2html.tags.Tag","j2html.tags.IInstance"),
interfaceNameFromAttribute(attr.attr).substring(1),
attr
);
if (!delete) {
System.out.println("writing to "+path);
Files.write(path, interfaceStr.getBytes());
}
}
}
private static String getPackage(){
return "package j2html.tags.attributes;\n";
}
private static String makeReturnTypeAndMethodName(final String name){
return "default "+ "T "+name;
}
private static String getInterfaceTemplate(
final String interfaceName,
final Optional<String> optExtends,
final List<String> imports,
final String interfaceNameSimple,
final AttrD attrD
){
final StringBuilder sb = new StringBuilder();
sb.append(getPackage());
sb.append("\n");
for(String importName : imports){
sb.append("import ").append(importName).append(";\n");
}
sb.append("\n");
sb.append("public interface ")
.append(interfaceName);
optExtends.ifPresent(ext -> sb.append(" extends ").append(ext).append(" "));
sb.append(" {\n");
//interface contents
/*
IFormAction<T extends Tag> extends IInstance<T>
default T withFormAction(String formAction){
return self().attr("formaction", formAction);
}
*/
//IMPORTANT: '_' added as suffix to mitigate problems
//where attributes are java keywords. Just to make it consistent and avoid special cases.
final String attrName = interfaceNameSimple.toLowerCase();
final String paramName = attrName+"_";
//depending on if the attribute has an argument or not,
//generate methods according to the convention in Tag.java
// arg -> with$ATTR(arg), withCond$ATTR(condition, arg)
// no arg -> is$ATTR(), withCond$ATTR(condition)
//append the 'with$ATTR' method
writeAttributeMethod(interfaceNameSimple, attrD, sb, attrName, paramName);
writeAttributeMethodCond(interfaceNameSimple, attrD, sb, attrName, paramName);
sb.append("}\n");
return sb.toString();
}
private static void addAttributeNoArg(final StringBuilder sb, final String attrName){
//generate the code to add an attribute without an argument
//there are some special attributes
//which do take an argument, but where the argument
//is boolean (meaning on/off, yes/no and the like)
sb.append("self().attr(\"");
if (attrName.equals("autocomplete")){
sb.append(attrName).append("\",\"on\"");
} else {
sb.append(attrName).append("\"");
}
sb.append(");\n");
}
private static void writeAttributeMethodCond(String interfaceNameSimple, AttrD attrD, StringBuilder sb, String attrName, String paramName) {
sb.append(makeReturnTypeAndMethodName("withCond"+interfaceNameSimple));
if(attrD.hasArgument){
//add a variant where you can specify the argument
sb.append("(final boolean enable, final String ").append(paramName).append(") {");
sb.append("if (enable){\n");
sb.append("self().attr(\"").append(attrName).append("\", ").append(paramName).append(");\n");
sb.append("}\n");
sb.append("return self();\n");
}else{
//add a variant where you can toggle the attribute
sb.append("(final boolean enable) {");
sb.append("if (enable){\n");
addAttributeNoArg(sb, attrName);
sb.append("}\n");
sb.append("return self();\n");
}
sb.append("}\n");
}
private static void writeAttributeMethod(String interfaceNameSimple, AttrD attrD, StringBuilder sb, String attrName, String paramName) {
sb.append(makeReturnTypeAndMethodName(
((attrD.hasArgument)?"with":"is")+interfaceNameSimple)
);
if(attrD.hasArgument){
//add a variant where you can specify the argument
sb.append("(final String ").append(paramName).append(") {")
.append("return self().attr(\"").append(attrName).append("\", ").append(paramName).append(");\n");
}else{
//add a variant where you can toggle the attribute
sb.append("() {");
addAttributeNoArg(sb, attrName);
sb.append("return self();\n");
}
sb.append("}\n");
}
public static String interfaceNameFromAttribute(String attribute){
String res = attribute.substring(0,1).toUpperCase()+attribute.substring(1);
return "I" + res;
}
private static Path makePath(String tagLowerCase, final Path absPath){
final String filename = interfaceNameFromAttribute(tagLowerCase)+".java";
return Paths.get(absPath.toString(),relPath,filename);
}
}

@ -0,0 +1,166 @@
package com.j2html.codegen.generators;
import com.j2html.codegen.GeneratorUtil;
import com.j2html.codegen.model.AttributesList;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import static com.j2html.codegen.generators.TagCreatorCodeGenerator.containerTags;
import static com.j2html.codegen.generators.TagCreatorCodeGenerator.emptyTags;
public final class SpecializedTagClassCodeGenerator {
private static final String relPath = "tags/specialized";
public static void generate(final Path absPath, final boolean delete) throws IOException {
//delete all files in the directory for fresh generation
final Path dir = Paths.get(absPath.toString(),relPath);
GeneratorUtil.deleteAllFilesInDir(dir);
//the delete argument serves to give the possibility
//to delete the classes that were written before
System.out.println("// EmptyTags, generated in " + SpecializedTagClassCodeGenerator.class);
for (final String tag : emptyTags()) {
final String className = classNameFromTag(tag);
final Path path = makePath(absPath,tag);
final List<String> interfaceNames = getInterfaceNamesForTag(tag);
final String classString =
getClassTemplate(
className,
Optional.of("EmptyTag<"+className+">"),
Arrays.asList(
"j2html.tags.EmptyTag",
"j2html.tags.attributes.*"
),
tag,
interfaceNames
);
/*
public InputTag() {
super("input");
}
*/
if(!delete){
System.out.println("writing to "+path);
Files.write(path, classString.getBytes());
}
}
System.out.println("// ContainerTags, generated in " + SpecializedTagClassCodeGenerator.class);
for (final String tag : containerTags()) {
final Path path = makePath(absPath, tag);
final String className = classNameFromTag(tag);
final List<String> interfaceNames = getInterfaceNamesForTag(tag);
final String classString =
getClassTemplate(
className,
Optional.of("ContainerTag<"+className+">"),
Arrays.asList(
"j2html.tags.ContainerTag",
"j2html.tags.attributes.*"
),
tag,
interfaceNames
);
if(delete){
if(Files.exists(path)) {
System.out.println("deleting " + path);
Files.delete(path);
}
}else {
System.out.println("writing to "+path);
Files.write(path, classString.getBytes());
}
}
}
public static String classNameFromTag(String tageNameLowerCase){
String res = tageNameLowerCase.substring(0,1).toUpperCase()+tageNameLowerCase.substring(1);
return res + "Tag";
}
private static Path makePath(final Path absPath, String tagLowerCase){
final String filename = classNameFromTag(tagLowerCase)+".java";
return Paths.get(absPath.toString(),relPath,filename);
}
private static String getPackage(){
return "package j2html.tags.specialized;\n";
}
private static String getClassTemplate(
final String className,
final Optional<String> optExtends,
final List<String> imports,
final String tag,
final List<String> interfaces
){
final StringBuilder sb = new StringBuilder();
sb.append(getPackage());
sb.append("\n");
for(String importName : imports){
sb.append("import ").append(importName).append(";\n");
}
sb.append("\n");
sb.append("public final class ")
.append(className)
.append(" ");
optExtends.ifPresent(ext -> sb.append("extends ").append(ext).append(" "));
//add the 'implements' clause
if(!interfaces.isEmpty()) {
sb.append("\n");
sb.append("implements ");
final List<String> genericInterfaceNames
= interfaces.stream().map(iName -> iName+"<"+className+">")
.collect(Collectors.toList());
sb.append(
String.join(",", genericInterfaceNames)
);
}
sb.append(" {\n");
//class contents
sb.append("public ")
.append(className)
.append("() {")
.append("super(\"").append(tag).append("\");")
.append("}\n");
sb.append("}\n");
return sb.toString();
}
private static List<String> getInterfaceNamesForTag(final String tagNameLowercase){
return AttributesList.getCustomAttributesForHtmlTag(tagNameLowercase)
.stream()
.map(
AttributeInterfaceCodeGenerator::interfaceNameFromAttribute
).collect(Collectors.toList());
}
}

@ -0,0 +1,189 @@
package com.j2html.codegen.generators;
import java.util.Arrays;
import java.util.List;
public final class TagCreatorCodeGenerator {
public static void print() {
System.out.println("// EmptyTags, generated in " + TagCreatorCodeGenerator.class);
for (String tag : emptyTags()) {
final String className = SpecializedTagClassCodeGenerator.classNameFromTag(tag);
final String publicstaticTypeMethod = "public static "+className+" "+tag+" ";
final String castReturn = " return ("+className+") ";
final String construct = " new "+className+"()";
String emptyA1 = publicstaticTypeMethod + "()";
String emptyA2 = "{ return "+construct+"; }";
// Attr shorthands
String emptyB1 = publicstaticTypeMethod + "(Attr.ShortForm shortAttr)";
String emptyB2 = "{ "+castReturn+" Attr.addTo("+construct+", shortAttr); }";
// Print
System.out.println(String.format("%-80s%1s", emptyA1, emptyA2));
System.out.println(String.format("%-80s%1s", emptyB1, emptyB2));
System.out.println();
}
System.out.println("// ContainerTags, generated in " + TagCreatorCodeGenerator.class);
for (String tag : containerTags()) {
final String className = SpecializedTagClassCodeGenerator.classNameFromTag(tag);
final String publicstaticTypeMethod = "public static "+className+" "+tag+" ";
final String castReturn = " return ("+className+") ";
final String construct = " new "+className+"()";
String containerA1 = publicstaticTypeMethod+ "()";
String containerA2 = "{ "+castReturn + construct + "; }";
String containerB1 = publicstaticTypeMethod + "(String text)";
String containerB2 = "{ "+castReturn + construct + ".withText(text); }";
String containerC1 = publicstaticTypeMethod + "(DomContent... dc)";
String containerC2 = "{ "+castReturn + construct+".with(dc); }";
// Attr shorthands
String containerD1 = publicstaticTypeMethod + "(Attr.ShortForm shortAttr)";
String containerD2 = "{ "+castReturn+" Attr.addTo("+construct+", shortAttr); }";
String containerE1 = publicstaticTypeMethod + "(Attr.ShortForm shortAttr, String text)";
String containerE2 = "{ "+castReturn+" Attr.addTo("+construct+".withText(text), shortAttr); }";
String containerF1 = publicstaticTypeMethod + "(Attr.ShortForm shortAttr, DomContent... dc)";
String containerF2 = "{ "+castReturn+" Attr.addTo("+construct+".with(dc), shortAttr); }";
// Print
System.out.println(String.format("%-80s%1s", containerA1, containerA2));
System.out.println(String.format("%-80s%1s", containerB1, containerB2));
System.out.println(String.format("%-80s%1s", containerC1, containerC2));
System.out.println(String.format("%-80s%1s", containerD1, containerD2));
System.out.println(String.format("%-80s%1s", containerE1, containerE2));
System.out.println(String.format("%-80s%1s", containerF1, containerF2));
System.out.println();
}
}
// This is a method that contains all ContainerTags, there is nothing below it
public static List<String> emptyTags() {
return Arrays.asList(
"area",
"base",
"br",
"col",
//"!DOCTYPE html",
"embed",
"hr",
"img",
"input",
"keygen",
"link",
"meta",
"param",
"source",
"track",
"wbr"
);
}
public static List<String> containerTags() {
return Arrays.asList(
"a",
"abbr",
"address",
"article",
"aside",
"audio",
"b",
"bdi",
"bdo",
"blockquote",
"body",
"button",
"canvas",
"caption",
"cite",
"code",
"colgroup",
"data",
"datalist",
"dd",
"del",
"details",
"dfn",
"dialog",
"div",
"dl",
"dt",
"em",
"fieldset",
"figcaption",
"figure",
"footer",
"form",
"h1",
"h2",
"h3",
"h4",
"h5",
"h6",
"head",
"header",
"html",
"i",
"iframe",
"ins",
"kbd",
"label",
"legend",
"li",
"main",
"map",
"mark",
"menu",
"menuitem",
"meter",
"nav",
"noscript",
"object",
"ol",
"optgroup",
"option",
"output",
"p",
"picture",
"pre",
"progress",
"q",
"rp",
"rt",
"ruby",
"s",
"samp",
"script",
"section",
"select",
"slot",
"small",
"span",
"strong",
"style",
"sub",
"summary",
"sup",
"table",
"tbody",
"td",
"template",
"textarea",
"tfoot",
"th",
"thead",
"time",
"title",
"tr",
"u",
"ul",
"var",
"video"
);
}
}

@ -0,0 +1,94 @@
package com.j2html.codegen.generators;
import com.j2html.codegen.wattsi.AttributeDefinition;
import com.j2html.codegen.wattsi.ElementDefinition;
import com.j2html.codegen.wattsi.WattsiSource;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import javax.lang.model.element.Modifier;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
public class WattsiGenerator {
public static void main(String... args) throws IOException {
Path source = Paths.get(args[0]);
Document doc = Jsoup.parse(source.toFile(), "UTF-8", "https://html.spec.whatwg.org/");
WattsiSource wattsi = new WattsiSource(doc);
List<ElementDefinition> elements = wattsi.elementDefinitions();
List<AttributeDefinition> attributes = wattsi.attributeDefinitions();
// for (ElementDefinition element : elements) {
// System.out.println((element.isObsolete() ? "!" : "") + element.name());
// for (AttributeDefinition attribute : attributes) {
// if (attribute.appliesTo(element)) {
// System.out.println(" " + (attribute.isObsolete() ? "!" : "") + attribute.name());
// }
// }
// System.out.println();
// }
for (ElementDefinition element : elements) {
ClassName className = ClassName.get(
"com.j2html",
capitalize(element.name()) + "Tag"
);
TypeSpec.Builder type = TypeSpec.classBuilder(className)
.addModifiers(Modifier.PUBLIC);
if (element.isObsolete()) {
type.addAnnotation(Deprecated.class);
}
for (AttributeDefinition attribute : attributes) {
if (attribute.appliesTo(element)) {
String name = methodName("with", attribute.name().split("-"));
MethodSpec.Builder setter = MethodSpec.methodBuilder(name)
.addModifiers(Modifier.PUBLIC)
.returns(className)
.addStatement("return this");
if(attribute.isObsolete()){
setter.addAnnotation(Deprecated.class);
}
type.addMethod(setter.build());
}
}
System.out.println(type.build().toString());
}
// System.out.println(doc.select("dfn"));
}
private static String methodName(String prefix, String... words){
String[] tmp = new String[words.length + 1];
tmp[0] = prefix;
for(int i = 0; i < words.length; i++){
tmp[i+1] = words[i];
}
return methodName(tmp);
}
private static String methodName(String... words){
String[] camelCase = new String[words.length];
camelCase[0] = words[0];
for(int i = 1; i < words.length; i++){
camelCase[i] = capitalize(words[i]);
}
return String.join("", camelCase);
}
private static String capitalize(String word){
return word.substring(0,1).toUpperCase() + word.substring(1).toLowerCase();
}
}

@ -0,0 +1,24 @@
package com.j2html.codegen.model;
public final class AttrD {
//attribute descriptor
public final String attr;
public final boolean hasArgument;
//the html tags that this attribute can be used on
public final String[] tags;
public AttrD(final String attr, boolean hasArgument){
this.attr = attr;
this.hasArgument = hasArgument;
this.tags = new String[]{};
}
public AttrD(final String attr, boolean hasArgument, final String... tags) {
this.attr = attr;
this.hasArgument = hasArgument;
this.tags = tags;
}
}

@ -0,0 +1,182 @@
package com.j2html.codegen.model;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public final class AttributesList {
//https://www.w3schools.com/tags/ref_attributes.asp
public static List<String> getCustomAttributesForHtmlTag(final String tagLowercase){
final List<String> attrs = new ArrayList<>();
for(AttrD attrD : attributesDescriptive()){
if(
Arrays.asList(attrD.tags).contains(tagLowercase)
){
attrs.add(attrD.attr);
}
}
return attrs;
}
public static List<AttrD> attributesDescriptive() {
return Arrays.asList(
new AttrD("accept", true, "input"),
//new AttrD("accept-charset","form"), //contains dashes, TODO
//new AttrD("accesskey"), //global attribute
new AttrD("action", true, "form"),
//"align", not supported in HTML5
new AttrD("alt", true, "area","img","input"),
new AttrD("async", false, "script"),
new AttrD("autocomplete", false, "form","input"),
new AttrD("autofocus", false, "button","input","select","textarea"),
new AttrD("autoplay", false, "audio","video"),
//"bgcolor", not supported in HTMTL5
//"border", not supported in HTML5
new AttrD("charset", true, "meta","script"),
new AttrD("checked", false, "input"),
new AttrD("cite", true, "blockquote","del","ins","q"),
//"class" already implemented in Tag.java // global attribute
new AttrD("cols", true, "textarea"),
new AttrD("colspan", true, "td","th"),
new AttrD("content", true, "meta"),
//"contenteditable" global attribute, should be in Tag.java
new AttrD("controls", false, "audio","video"),
new AttrD("coords", true, "area"),
new AttrD("data", true, "object"),
new AttrD("datetime", true, "del","ins","time"),
new AttrD("default", false, "track"),
new AttrD("defer", false, "script"),
//new AttrD("dir"), //global attribute
new AttrD("dirname", true, "input","textarea"),
new AttrD("disabled",false, "button","fieldset","input","optgroup","option","select","textarea"),
new AttrD("download",false, "a","area"),
//new AttrD("draggable") global attribute, should be in Tag.java
new AttrD("enctype", true, "form"),
new AttrD("for", true, "label","output"),
new AttrD("form", true, "button","fieldset","input","label","meter","object","output","select","textarea"),
new AttrD("formaction", true, "button","input"),
new AttrD("headers", true, "td","th"),
new AttrD("height", true, "canvas","embed","iframe","img","input","object","video"),
//new AttrD("hidden"), global attribute
new AttrD("high", true, "meter"),
new AttrD("href", true, "a","area","base","link"),
new AttrD("hreflang", true, "a","area","link"),
//"http-equiv", //TODO: '-' is problematic in code generation
//"id" global attribute, should be in Tag.java
new AttrD("ismap", false, "img"),
new AttrD("kind", true, "track"),
new AttrD("label", true, "track","option","optgroup"),
//"lang" global attribute, should be in Tag.java
new AttrD("list", true, "input"),
new AttrD("loop", false, "audio","video"),
new AttrD("low", true, "meter"),
new AttrD("max", true, "input","meter","progress"),
new AttrD("maxlength", true, "input","textarea"),
new AttrD("media", true, "a","area","link","source","style"),
new AttrD("method", true, "form"),
new AttrD("min", true, "input","meter"),
new AttrD("multiple", false, "input","select"),
new AttrD("muted", false, "video","audio"),
new AttrD("name", true, "button","fieldset","form","iframe","input","map","meta","object","output","param","select","slot","textarea"),
new AttrD("novalidate", false, "form"),
new AttrD("onabort", true, "audio","embed","img","object","video"),
new AttrD("onafterprint", true, "body"),
new AttrD("onbeforeprint", true, "body"),
new AttrD("onbeforeunload", true, "body"),
//new AttrD("onblur"), global attribute
new AttrD("oncanplay", true, "audio","embed","object","video"),
new AttrD("oncanplaythrough", true, "audio","video"),
/* a bunch of event attributes that are on all visible elements (so should be in Tag.java)
"onchange",
"onclick",
"oncontextmenu",
"oncopy",
*/
new AttrD("oncuechange", true, "track"),
/*
"oncut",
...
"ondrop",
*/
new AttrD("ondurationchange", true, "audio","video"),
new AttrD("onemptied", true, "audio","video"),
new AttrD("onended", true, "audio","video"),
new AttrD("onerror", true, "audio","body","embed","img","object","script","style","video"),
//new AttrD("onfocus"),// global attribute
new AttrD("onhashchange", true, "body"),
// ... a bunch of event attributes visible on all elements
new AttrD("onload", true, "body","iframe","img","input","link","script","style"),
new AttrD("onloadeddata", true, "audio","video"),
new AttrD("onloadedmetadata", true, "audio","video"),
new AttrD("onloadstart", true, "audio","video"),
// ... a bunch of event attributes visible on all elements
new AttrD("onoffline", true, "body"),
new AttrD("ononline", true, "body"),
new AttrD("onpagehide", true, "body"),
new AttrD("onpageshow", true, "body"),
//new AttrD("onpaste"),// global attribute
new AttrD("onpause", true, "audio","video"),
new AttrD("onplay", true, "audio","video"),
new AttrD("onplaying", true, "audio","video"),
new AttrD("onpopstate", true, "body"),
new AttrD("onprogress", true, "audio","video"),
new AttrD("onratechange", true, "audio","video"),
new AttrD("onreset", true, "form"),
new AttrD("onresize", true, "body"),
//new AttrD("onscroll"), //global attribute
new AttrD("onsearch", true, "input"),
new AttrD("onseeked", true, "audio","video"),
new AttrD("onseeking", true, "audio","video"),
//new AttrD("onselect"), //global attribute
new AttrD("onstalled", true, "audio","video"),
new AttrD("onstorage", true, "body"),
new AttrD("onsubmit", true, "form"),
new AttrD("onsuspend", true, "audio","video"),
new AttrD("ontimeupdate", true, "audio","video"),
new AttrD("ontoggle", true, "details"),
new AttrD("onunload", true, "body"),
new AttrD("onvolumechanged", true, "audio","video"),
new AttrD("onwaiting", true, "audio","video"),
//new AttrD("onwheel"), //global attribute
new AttrD("open", false, "details"),
new AttrD("optimum", true, "meter"),
new AttrD("pattern", true, "input"),
new AttrD("placeholder", true, "input","textarea"),
new AttrD("poster", true, "video"),
new AttrD("preload", true, "audio","video"),
new AttrD("readonly", false, "input","textarea"),
new AttrD("rel", true, "a","area","form","link"),
new AttrD("required", false, "input","select","textarea"),
new AttrD("reversed", false, "ol"),
new AttrD("rows", true, "textarea"),
new AttrD("rowspan", true, "td","th"),
new AttrD("sandbox", false, "iframe"),
new AttrD("scope", true, "th"),
new AttrD("selected", false, "option"),
new AttrD("shape", true, "area"),
new AttrD("size", true, "input","select"),
new AttrD("sizes", true, "img","link","source"),
new AttrD("span", true, "col","colgroup"),
//new AttrD("spellcheck"), //global attribute
new AttrD("src", true, "audio","embed","iframe","img","input","script","source","track","video"),
new AttrD("srcdoc", true, "iframe"),
new AttrD("srclang", true, "track"),
new AttrD("srcset", true, "img","source"),
new AttrD("start", true, "ol"),
new AttrD("step", true, "input"),
//new AttrD("style"), //global attribute
//new AttrD("tabindex"), //global attribute
new AttrD("target", true, "a","area","base","form"),
//new AttrD("title"), //global attribute
//new AttrD("translate"),// global attribute
new AttrD("type", true, "a","button","embed","input","link","menu","object","script","source","style"),
new AttrD("usemap", true, "img","object"),
new AttrD("value", true, "button","data","input","li","option","meter","progress","param"),
new AttrD("width", true, "canvas","embed","iframe","img","input","object","video"),
new AttrD("wrap", true, "textarea")
);
}
}

@ -0,0 +1,10 @@
package com.j2html.codegen.wattsi;
public interface AttributeDefinition {
String name();
boolean appliesTo(ElementDefinition element);
boolean isObsolete();
}

@ -0,0 +1,11 @@
package com.j2html.codegen.wattsi;
public interface ElementDefinition {
String name();
boolean isSelfClosing();
boolean isObsolete();
}

@ -0,0 +1,209 @@
package com.j2html.codegen.wattsi;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.nodes.Node;
import org.jsoup.nodes.TextNode;
import org.jsoup.select.Elements;
import java.util.*;
import java.util.stream.Collectors;
import static java.util.stream.Collectors.toList;
public class WattsiSource {
private final Document doc;
private final Set<Reference> obsolete = new HashSet<>();
public WattsiSource(Document doc) {
this.doc = doc;
// Find where obsolete elements are defined or referenced.
Elements obsoleteElements = doc.select("p:contains(Elements in the following list are entirely obsolete) + dl");
// Convert definitions into references to record obsolete elements.
obsoleteElements.select("dt > dfn[element]")
.stream()
.map(WattsiElement::new)
.map(WattsiElement::reference)
.forEach(obsolete::add);
// Extract references to record obsolete elements.
obsoleteElements.select("dt > code")
.stream()
.map(Element::childNodes)
.map(Reference::from)
.forEach(obsolete::add);
// Find where obsolete attributes are defined or referenced.
Elements obsoleteAttributes = doc.select("p:contains(The following attributes are obsolete) + dl");
// Convert definitions into references to record obsolete attributes.
obsoleteAttributes.select("dt > dfn[element-attr]").stream()
.map(WattsiAttribute::new)
.map(WattsiAttribute::reference)
.forEach(obsolete::add);
// System.out.println(obsoleteAttributes.select("dt"));
// obsoleteAttributes.select("dt > code").stream()
// .map(Element::childNodes)
// .map(Reference::from)
// .forEach(System.err::println);
// System.out.println(
// doc.select("dfn[obsolete]")
// );
}
public List<ElementDefinition> elementDefinitions() {
return doc.select("dfn[element]").stream()
.map(WattsiElement::new)
.collect(toList());
}
public List<AttributeDefinition> attributeDefinitions() {
return doc.select("dfn[element-attr]").stream()
.map(WattsiAttribute::new)
.collect(toList());
}
public class WattsiElement implements ElementDefinition {
private final Element dfn;
WattsiElement(Element dfn) {
if (!"dfn".equals(dfn.tagName())) {
throw new IllegalArgumentException("Element cannot be defined from: " + dfn);
}
if (!dfn.hasAttr("element")) {
throw new IllegalArgumentException("Does not define an element: " + dfn);
}
if (dfn.childrenSize() != 1) {
throw new IllegalArgumentException("Element cannot have multiple definitions: " + dfn);
}
this.dfn = dfn;
}
private Reference reference() {
return Reference.from(dfn.childNodes());
}
@Override
public String name() {
if (dfn.hasAttr("data-x")) {
return dfn.attr("data-x");
}
return Reference.from(dfn.childNodes()).key;
}
@Override
public boolean isSelfClosing() {
return false;
}
@Override
public boolean isObsolete() {
return obsolete.contains(reference());
}
}
public class WattsiAttribute implements AttributeDefinition {
private final Element dfn;
WattsiAttribute(Element dfn) {
if (!"dfn".equals(dfn.tagName())) {
throw new IllegalArgumentException("Attribute cannot be defined from: " + dfn);
}
if (!dfn.hasAttr("element-attr")) {
throw new IllegalArgumentException("Does not define an attribute: " + dfn);
}
if (dfn.childrenSize() != 1) {
throw new IllegalArgumentException("Attribute cannot have multiple definitions: " + dfn);
}
this.dfn = dfn;
}
private Reference reference() {
return Reference.from(dfn.childNodes());
}
@Override
public String name() {
return reference().text;
}
private List<String> targets() {
if (dfn.hasAttr("for")) {
return Arrays.asList(dfn.attr("for").trim().split(","));
}
return new ArrayList<>();
}
@Override
public boolean appliesTo(ElementDefinition element) {
return targets().contains(element.name());
}
@Override
public boolean isObsolete() {
return obsolete.contains(reference());
}
}
private static class Reference {
private final String key;
private final String text;
Reference(String key, String text) {
this.key = key;
this.text = text;
}
@Override
public String toString() {
return key + "[" + text + "]";
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Reference reference = (Reference) o;
return key.equals(reference.key);
}
@Override
public int hashCode() {
return Objects.hash(key);
}
public static Reference from(List<Node> nodes) {
if (nodes.stream().allMatch(n -> n instanceof TextNode)) {
String txt = nodes.stream()
.map(n -> (TextNode) n)
.map(TextNode::text)
.collect(Collectors.joining(" "));
return new Reference(txt, txt);
}
for (Node node : nodes) {
if (node instanceof Element) {
Element element = (Element) node;
if (element.is("code") || element.is("span")) {
if (element.hasAttr("data-x")) {
return new Reference(element.attr("data-x").toLowerCase(), element.text());
} else {
return new Reference(element.text().toLowerCase(), element.text());
}
}
}
}
return null;
}
}
}

@ -0,0 +1,16 @@
package com.j2html.codegen;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
public class AppTest
{
@Test
public void shouldAnswerWithTrue()
{
//dummy, just to conform to the default mvn
//directory layout
assertTrue( true );
}
}

@ -0,0 +1,77 @@
package com.j2html.codegen;
import com.j2html.codegen.generators.TagCreatorCodeGenerator;
import com.j2html.codegen.wattsi.ElementDefinition;
import com.j2html.codegen.wattsi.WattsiSource;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import static java.util.Collections.emptyList;
import static java.util.stream.Collectors.toList;
import static org.junit.Assert.assertEquals;
public class CodeGeneratorComplianceTests {
private WattsiSource specification;
@Before
public void setUp() throws IOException {
Path source = Paths.get("src","test","resources","2022-01.wattsi");
Document doc = Jsoup.parse(source.toFile(), "UTF-8", "https://html.spec.whatwg.org/");
specification = new WattsiSource(doc);
}
private Set<String> generatedElements(){
Set<String> elements = new HashSet<>();
elements.addAll(TagCreatorCodeGenerator.emptyTags());
elements.addAll(TagCreatorCodeGenerator.containerTags());
return elements;
}
private Set<String> specifiedElements(WattsiSource source){
Set<String> elements = new HashSet<>();
for(ElementDefinition element : source.elementDefinitions()){
elements.add(element.name());
}
return elements;
}
@Test
@Ignore
// TODO restore this test once a policy has been determined for obsolete elements.
public void all_wattsi_elements_are_defined_in_the_code_generator() {
Set<String> generated = generatedElements();
List<String> undefined = specification.elementDefinitions().stream()
.filter(element -> !element.isObsolete())
.filter(element -> !generated.contains(element.name()))
.map(ElementDefinition::name)
.collect(toList());
assertEquals("HTML elements are missing", emptyList(), undefined);
// Currently missing (and mostly deprecated):
// hgroup
}
@Test
public void only_wattsi_elements_are_defined_in_the_code_generator(){
Set<String> specified = specifiedElements(specification);
List<String> invalid = generatedElements().stream()
.filter(element -> !specified.contains(element))
.collect(toList());
assertEquals("HTML elements are invalid", emptyList(), invalid);
}
}

@ -0,0 +1,69 @@
package com.j2html.codegen;
import org.junit.Test;
import org.mockito.InOrder;
import java.util.function.Consumer;
import static org.mockito.Mockito.*;
public class ParserTest {
private void verifyParsing(String txt, Consumer<Parser.Listener> checks) {
Parser.Listener listener = mock(Parser.Listener.class);
Parser.parse(txt, listener);
checks.accept(listener);
}
@Test
public void an_empty_input_has_no_events() {
verifyParsing("", listener -> {
verifyNoInteractions(listener);
});
}
@Test
public void whitespace_has_no_events() {
verifyParsing(" \t\t\t\t", listener -> {
verifyNoInteractions(listener);
});
}
@Test
public void commented_lines_are_signaled() {
verifyParsing("#Comment 1.\n# Comment B?", listener -> {
InOrder order = inOrder(listener);
order.verify(listener).lineCommented(1, "#Comment 1.");
order.verify(listener).lineCommented(2, "# Comment B?");
});
}
@Test
public void node_definitions_are_signaled() {
verifyParsing("ELEMENT[a]\nEMPTY-ELEMENT[b]\nBOOLEAN[c]\nONOFF[d]\nSTRING[e]", listener -> {
InOrder order = inOrder(listener);
order.verify(listener).elementDefined(1, "a");
order.verify(listener).emptyElementDefined(2, "b");
order.verify(listener).booleanDefined(3, "c");
order.verify(listener).onOffDefined(4, "d");
order.verify(listener).stringDefined(5, "e");
});
}
@Test
public void attribute_definitions_are_signaled() {
verifyParsing("ATTRIBUTE[a:b]", listener -> {
InOrder order = inOrder(listener);
order.verify(listener).attributeDefined(1, "a", "b");
});
}
@Test
public void invalid_lines_are_signaled() {
verifyParsing("lol, I dunno!\nIt Broke...", listener -> {
InOrder order = inOrder(listener);
order.verify(listener).invalidLine(1, "lol, I dunno!");
order.verify(listener).invalidLine(2, "It Broke...");
});
}
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,622 @@
EMPTY-ELEMENT[area]
EMPTY-ELEMENT[base]
EMPTY-ELEMENT[br]
EMPTY-ELEMENT[col]
EMPTY-ELEMENT[embed]
EMPTY-ELEMENT[hr]
EMPTY-ELEMENT[img]
EMPTY-ELEMENT[input]
EMPTY-ELEMENT[keygen]
EMPTY-ELEMENT[link]
EMPTY-ELEMENT[meta]
EMPTY-ELEMENT[param]
EMPTY-ELEMENT[source]
EMPTY-ELEMENT[track]
EMPTY-ELEMENT[wbr]
ELEMENT[a]
ELEMENT[abbr]
ELEMENT[address]
ELEMENT[article]
ELEMENT[aside]
ELEMENT[audio]
ELEMENT[b]
ELEMENT[bdi]
ELEMENT[bdo]
ELEMENT[blockquote]
ELEMENT[body]
ELEMENT[button]
ELEMENT[canvas]
ELEMENT[caption]
ELEMENT[cite]
ELEMENT[code]
ELEMENT[colgroup]
ELEMENT[data]
ELEMENT[datalist]
ELEMENT[dd]
ELEMENT[del]
ELEMENT[details]
ELEMENT[dfn]
ELEMENT[dialog]
ELEMENT[div]
ELEMENT[dl]
ELEMENT[dt]
ELEMENT[em]
ELEMENT[fieldset]
ELEMENT[figcaption]
ELEMENT[figure]
ELEMENT[footer]
ELEMENT[form]
ELEMENT[h1]
ELEMENT[h2]
ELEMENT[h3]
ELEMENT[h4]
ELEMENT[h5]
ELEMENT[h6]
ELEMENT[head]
ELEMENT[header]
ELEMENT[html]
ELEMENT[i]
ELEMENT[iframe]
ELEMENT[ins]
ELEMENT[kbd]
ELEMENT[label]
ELEMENT[legend]
ELEMENT[li]
ELEMENT[main]
ELEMENT[map]
ELEMENT[mark]
ELEMENT[menu]
ELEMENT[menuitem]
ELEMENT[meter]
ELEMENT[nav]
ELEMENT[noscript]
ELEMENT[object]
ELEMENT[ol]
ELEMENT[optgroup]
ELEMENT[option]
ELEMENT[output]
ELEMENT[p]
ELEMENT[picture]
ELEMENT[pre]
ELEMENT[progress]
ELEMENT[q]
ELEMENT[rp]
ELEMENT[rt]
ELEMENT[ruby]
ELEMENT[s]
ELEMENT[samp]
ELEMENT[script]
ELEMENT[section]
ELEMENT[select]
ELEMENT[slot]
ELEMENT[small]
ELEMENT[span]
ELEMENT[strong]
ELEMENT[style]
ELEMENT[sub]
ELEMENT[summary]
ELEMENT[sup]
ELEMENT[table]
ELEMENT[tbody]
ELEMENT[td]
ELEMENT[template]
ELEMENT[textarea]
ELEMENT[tfoot]
ELEMENT[th]
ELEMENT[thead]
ELEMENT[time]
ELEMENT[title]
ELEMENT[tr]
ELEMENT[u]
ELEMENT[ul]
ELEMENT[var]
ELEMENT[video]
STRING[accept]
ATTRIBUTE[input:accept]
STRING[action]
ATTRIBUTE[form:action]
STRING[alt]
ATTRIBUTE[area:alt]
ATTRIBUTE[img:alt]
ATTRIBUTE[input:alt]
BOOLEAN[async]
ATTRIBUTE[script:async]
ONOFF[autocomplete]
ATTRIBUTE[form:autocomplete]
ATTRIBUTE[input:autocomplete]
BOOLEAN[autofocus]
ATTRIBUTE[button:autofocus]
ATTRIBUTE[input:autofocus]
ATTRIBUTE[select:autofocus]
ATTRIBUTE[textarea:autofocus]
BOOLEAN[autoplay]
ATTRIBUTE[audio:autoplay]
ATTRIBUTE[video:autoplay]
STRING[charset]
ATTRIBUTE[meta:charset]
ATTRIBUTE[script:charset]
BOOLEAN[checked]
ATTRIBUTE[input:checked]
STRING[cite]
ATTRIBUTE[blockquote:cite]
ATTRIBUTE[del:cite]
ATTRIBUTE[ins:cite]
ATTRIBUTE[q:cite]
STRING[cols]
ATTRIBUTE[textarea:cols]
STRING[colspan]
ATTRIBUTE[td:colspan]
ATTRIBUTE[th:colspan]
STRING[content]
ATTRIBUTE[meta:content]
BOOLEAN[controls]
ATTRIBUTE[audio:controls]
ATTRIBUTE[video:controls]
STRING[coords]
ATTRIBUTE[area:coords]
STRING[data]
ATTRIBUTE[object:data]
STRING[datetime]
ATTRIBUTE[del:datetime]
ATTRIBUTE[ins:datetime]
ATTRIBUTE[time:datetime]
BOOLEAN[default]
ATTRIBUTE[track:default]
BOOLEAN[defer]
ATTRIBUTE[script:defer]
STRING[dirname]
ATTRIBUTE[input:dirname]
ATTRIBUTE[textarea:dirname]
BOOLEAN[disabled]
ATTRIBUTE[button:disabled]
ATTRIBUTE[fieldset:disabled]
ATTRIBUTE[input:disabled]
ATTRIBUTE[optgroup:disabled]
ATTRIBUTE[option:disabled]
ATTRIBUTE[select:disabled]
ATTRIBUTE[textarea:disabled]
BOOLEAN[download]
ATTRIBUTE[a:download]
ATTRIBUTE[area:download]
STRING[enctype]
ATTRIBUTE[form:enctype]
STRING[for]
ATTRIBUTE[label:for]
ATTRIBUTE[output:for]
STRING[form]
ATTRIBUTE[button:form]
ATTRIBUTE[fieldset:form]
ATTRIBUTE[input:form]
ATTRIBUTE[label:form]
ATTRIBUTE[meter:form]
ATTRIBUTE[object:form]
ATTRIBUTE[output:form]
ATTRIBUTE[select:form]
ATTRIBUTE[textarea:form]
STRING[formaction]
ATTRIBUTE[button:formaction]
ATTRIBUTE[input:formaction]
STRING[headers]
ATTRIBUTE[td:headers]
ATTRIBUTE[th:headers]
STRING[height]
ATTRIBUTE[canvas:height]
ATTRIBUTE[embed:height]
ATTRIBUTE[iframe:height]
ATTRIBUTE[img:height]
ATTRIBUTE[input:height]
ATTRIBUTE[object:height]
ATTRIBUTE[video:height]
STRING[high]
ATTRIBUTE[meter:high]
STRING[href]
ATTRIBUTE[a:href]
ATTRIBUTE[area:href]
ATTRIBUTE[base:href]
ATTRIBUTE[link:href]
STRING[hreflang]
ATTRIBUTE[a:hreflang]
ATTRIBUTE[area:hreflang]
ATTRIBUTE[link:hreflang]
BOOLEAN[ismap]
ATTRIBUTE[img:ismap]
STRING[kind]
ATTRIBUTE[track:kind]
STRING[label]
ATTRIBUTE[track:label]
ATTRIBUTE[option:label]
ATTRIBUTE[optgroup:label]
STRING[list]
ATTRIBUTE[input:list]
BOOLEAN[loop]
ATTRIBUTE[audio:loop]
ATTRIBUTE[video:loop]
STRING[low]
ATTRIBUTE[meter:low]
STRING[max]
ATTRIBUTE[input:max]
ATTRIBUTE[meter:max]
ATTRIBUTE[progress:max]
STRING[maxlength]
ATTRIBUTE[input:maxlength]
ATTRIBUTE[textarea:maxlength]
STRING[media]
ATTRIBUTE[a:media]
ATTRIBUTE[area:media]
ATTRIBUTE[link:media]
ATTRIBUTE[source:media]
ATTRIBUTE[style:media]
STRING[method]
ATTRIBUTE[form:method]
STRING[min]
ATTRIBUTE[input:min]
ATTRIBUTE[meter:min]
BOOLEAN[multiple]
ATTRIBUTE[input:multiple]
ATTRIBUTE[select:multiple]
BOOLEAN[muted]
ATTRIBUTE[video:muted]
ATTRIBUTE[audio:muted]
STRING[name]
ATTRIBUTE[button:name]
ATTRIBUTE[fieldset:name]
ATTRIBUTE[form:name]
ATTRIBUTE[iframe:name]
ATTRIBUTE[input:name]
ATTRIBUTE[map:name]
ATTRIBUTE[meta:name]
ATTRIBUTE[object:name]
ATTRIBUTE[output:name]
ATTRIBUTE[param:name]
ATTRIBUTE[select:name]
ATTRIBUTE[slot:name]
ATTRIBUTE[textarea:name]
BOOLEAN[novalidate]
ATTRIBUTE[form:novalidate]
STRING[onabort]
ATTRIBUTE[audio:onabort]
ATTRIBUTE[embed:onabort]
ATTRIBUTE[img:onabort]
ATTRIBUTE[object:onabort]
ATTRIBUTE[video:onabort]
STRING[onafterprint]
ATTRIBUTE[body:onafterprint]
STRING[onbeforeprint]
ATTRIBUTE[body:onbeforeprint]
STRING[onbeforeunload]
ATTRIBUTE[body:onbeforeunload]
STRING[oncanplay]
ATTRIBUTE[audio:oncanplay]
ATTRIBUTE[embed:oncanplay]
ATTRIBUTE[object:oncanplay]
ATTRIBUTE[video:oncanplay]
STRING[oncanplaythrough]
ATTRIBUTE[audio:oncanplaythrough]
ATTRIBUTE[video:oncanplaythrough]
STRING[oncuechange]
ATTRIBUTE[track:oncuechange]
STRING[ondurationchange]
ATTRIBUTE[audio:ondurationchange]
ATTRIBUTE[video:ondurationchange]
STRING[onemptied]
ATTRIBUTE[audio:onemptied]
ATTRIBUTE[video:onemptied]
STRING[onended]
ATTRIBUTE[audio:onended]
ATTRIBUTE[video:onended]
STRING[onerror]
ATTRIBUTE[audio:onerror]
ATTRIBUTE[body:onerror]
ATTRIBUTE[embed:onerror]
ATTRIBUTE[img:onerror]
ATTRIBUTE[object:onerror]
ATTRIBUTE[script:onerror]
ATTRIBUTE[style:onerror]
ATTRIBUTE[video:onerror]
STRING[onhashchange]
ATTRIBUTE[body:onhashchange]
STRING[onload]
ATTRIBUTE[body:onload]
ATTRIBUTE[iframe:onload]
ATTRIBUTE[img:onload]
ATTRIBUTE[input:onload]
ATTRIBUTE[link:onload]
ATTRIBUTE[script:onload]
ATTRIBUTE[style:onload]
STRING[onloadeddata]
ATTRIBUTE[audio:onloadeddata]
ATTRIBUTE[video:onloadeddata]
STRING[onloadedmetadata]
ATTRIBUTE[audio:onloadedmetadata]
ATTRIBUTE[video:onloadedmetadata]
STRING[onloadstart]
ATTRIBUTE[audio:onloadstart]
ATTRIBUTE[video:onloadstart]
STRING[onoffline]
ATTRIBUTE[body:onoffline]
STRING[ononline]
ATTRIBUTE[body:ononline]
STRING[onpagehide]
ATTRIBUTE[body:onpagehide]
STRING[onpageshow]
ATTRIBUTE[body:onpageshow]
STRING[onpause]
ATTRIBUTE[audio:onpause]
ATTRIBUTE[video:onpause]
STRING[onplay]
ATTRIBUTE[audio:onplay]
ATTRIBUTE[video:onplay]
STRING[onplaying]
ATTRIBUTE[audio:onplaying]
ATTRIBUTE[video:onplaying]
STRING[onpopstate]
ATTRIBUTE[body:onpopstate]
STRING[onprogress]
ATTRIBUTE[audio:onprogress]
ATTRIBUTE[video:onprogress]
STRING[onratechange]
ATTRIBUTE[audio:onratechange]
ATTRIBUTE[video:onratechange]
STRING[onreset]
ATTRIBUTE[form:onreset]
STRING[onresize]
ATTRIBUTE[body:onresize]
STRING[onsearch]
ATTRIBUTE[input:onsearch]
STRING[onseeked]
ATTRIBUTE[audio:onseeked]
ATTRIBUTE[video:onseeked]
STRING[onseeking]
ATTRIBUTE[audio:onseeking]
ATTRIBUTE[video:onseeking]
STRING[onstalled]
ATTRIBUTE[audio:onstalled]
ATTRIBUTE[video:onstalled]
STRING[onstorage]
ATTRIBUTE[body:onstorage]
STRING[onsubmit]
ATTRIBUTE[form:onsubmit]
STRING[onsuspend]
ATTRIBUTE[audio:onsuspend]
ATTRIBUTE[video:onsuspend]
STRING[ontimeupdate]
ATTRIBUTE[audio:ontimeupdate]
ATTRIBUTE[video:ontimeupdate]
STRING[ontoggle]
ATTRIBUTE[details:ontoggle]
STRING[onunload]
ATTRIBUTE[body:onunload]
STRING[onvolumechanged]
ATTRIBUTE[audio:onvolumechanged]
ATTRIBUTE[video:onvolumechanged]
STRING[onwaiting]
ATTRIBUTE[audio:onwaiting]
ATTRIBUTE[video:onwaiting]
BOOLEAN[open]
ATTRIBUTE[details:open]
STRING[optimum]
ATTRIBUTE[meter:optimum]
STRING[pattern]
ATTRIBUTE[input:pattern]
STRING[placeholder]
ATTRIBUTE[input:placeholder]
ATTRIBUTE[textarea:placeholder]
STRING[poster]
ATTRIBUTE[video:poster]
STRING[preload]
ATTRIBUTE[audio:preload]
ATTRIBUTE[video:preload]
BOOLEAN[readonly]
ATTRIBUTE[input:readonly]
ATTRIBUTE[textarea:readonly]
STRING[rel]
ATTRIBUTE[a:rel]
ATTRIBUTE[area:rel]
ATTRIBUTE[form:rel]
ATTRIBUTE[link:rel]
BOOLEAN[required]
ATTRIBUTE[input:required]
ATTRIBUTE[select:required]
ATTRIBUTE[textarea:required]
BOOLEAN[reversed]
ATTRIBUTE[ol:reversed]
STRING[rows]
ATTRIBUTE[textarea:rows]
STRING[rowspan]
ATTRIBUTE[td:rowspan]
ATTRIBUTE[th:rowspan]
BOOLEAN[sandbox]
ATTRIBUTE[iframe:sandbox]
STRING[scope]
ATTRIBUTE[th:scope]
BOOLEAN[selected]
ATTRIBUTE[option:selected]
STRING[shape]
ATTRIBUTE[area:shape]
STRING[size]
ATTRIBUTE[input:size]
ATTRIBUTE[select:size]
STRING[sizes]
ATTRIBUTE[img:sizes]
ATTRIBUTE[link:sizes]
ATTRIBUTE[source:sizes]
STRING[span]
ATTRIBUTE[col:span]
ATTRIBUTE[colgroup:span]
STRING[src]
ATTRIBUTE[audio:src]
ATTRIBUTE[embed:src]
ATTRIBUTE[iframe:src]
ATTRIBUTE[img:src]
ATTRIBUTE[input:src]
ATTRIBUTE[script:src]
ATTRIBUTE[source:src]
ATTRIBUTE[track:src]
ATTRIBUTE[video:src]
STRING[srcdoc]
ATTRIBUTE[iframe:srcdoc]
STRING[srclang]
ATTRIBUTE[track:srclang]
STRING[srcset]
ATTRIBUTE[img:srcset]
ATTRIBUTE[source:srcset]
STRING[start]
ATTRIBUTE[ol:start]
STRING[step]
ATTRIBUTE[input:step]
STRING[target]
ATTRIBUTE[a:target]
ATTRIBUTE[area:target]
ATTRIBUTE[base:target]
ATTRIBUTE[form:target]
STRING[type]
ATTRIBUTE[a:type]
ATTRIBUTE[button:type]
ATTRIBUTE[embed:type]
ATTRIBUTE[input:type]
ATTRIBUTE[link:type]
ATTRIBUTE[menu:type]
ATTRIBUTE[object:type]
ATTRIBUTE[script:type]
ATTRIBUTE[source:type]
ATTRIBUTE[style:type]
STRING[usemap]
ATTRIBUTE[img:usemap]
ATTRIBUTE[object:usemap]
STRING[value]
ATTRIBUTE[button:value]
ATTRIBUTE[data:value]
ATTRIBUTE[input:value]
ATTRIBUTE[li:value]
ATTRIBUTE[option:value]
ATTRIBUTE[meter:value]
ATTRIBUTE[progress:value]
ATTRIBUTE[param:value]
STRING[width]
ATTRIBUTE[canvas:width]
ATTRIBUTE[embed:width]
ATTRIBUTE[iframe:width]
ATTRIBUTE[img:width]
ATTRIBUTE[input:width]
ATTRIBUTE[object:width]
ATTRIBUTE[video:width]
STRING[wrap]
ATTRIBUTE[textarea:wrap]

@ -15,26 +15,18 @@ dependencies {
testImplementation gradleTestKit()
}
gradlePlugin {
plugins {
jaccPlugin {
id = 'org.xbib.gradle.plugin.jacc'
implementationClass = 'org.xbib.gradle.plugin.jacc.JaccPlugin'
}
}
}
if (project.hasProperty('gradle.publish.key')) {
pluginBundle {
website = scmUrl
vcsUrl = scmUrl
gradlePlugin {
website = 'https://xbib.org/joerg/gradle-plugins/src/branch/main/gradle-plugin-jacc'
vcsUrl = 'https://xbib.org/joerg/gradle-plugins'
plugins {
jaccPlugin {
id = 'org.xbib.gradle.plugin.jacc'
implementationClass = 'org.xbib.gradle.plugin.jacc.JaccPlugin'
version = project.version
description = 'Gradle Jacc plugin'
displayName = 'Gradle Jacc plugin'
tags = ['jacc']
tags.set(['jacc'])
}
}
}

@ -1 +1,2 @@
version = 1.4.0
name = gradle-plugin-jacc
version = 3.1.0

@ -26,8 +26,12 @@ class JaccTask extends DefaultTask {
static String getPackageName(File file) {
String string = file.readLines().find { line ->
line.startsWith('package')
line.startsWith('package ') || line.startsWith('%package ')
}
return string == null ? '' : string.substring(8, string.length() - 1)
if (string == null) {
return ''
}
int pos = string.indexOf(' ')
string = string.substring(pos + 1).replace(';', '')
}
}

@ -56,10 +56,12 @@ sourceSets {
.build()
assertEquals(TaskOutcome.SUCCESS, result.task(":build").getOutcome())
File file = new File("${System.getProperty('user.dir')}/build/my-generated-sources/jacc")
File file = new File("${System.getProperty('user.dir')}/build/my-generated-sources/jacc/org/xbib/gradle/plugin/jacc/calc")
if (file.exists()) {
List<File> list = Arrays.asList(file.listFiles())
assertEquals(2, list.size())
} else {
fail()
}
}
}

@ -6,6 +6,7 @@
// ... enter arithmetic expressions ... hit EOF to terminate
//
%package org.xbib.gradle.plugin.jacc.calc
%class Calc
%interface CalcTokens
%semantic int : yylval

@ -0,0 +1,10 @@
This work is based on
https://github.com/kordamp/jdeps-gradle-plugin
by Andres Almiray
as of Feb 17, 2024
License: Apache 2.0

@ -0,0 +1,32 @@
plugins {
id 'java-gradle-plugin'
alias(libs.plugins.publish)
}
apply plugin: 'java-gradle-plugin'
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-jdeps'
vcsUrl = 'https://xbib.org/joerg/gradle-plugins'
plugins {
jdepsPlugin {
id = 'org.xbib.gradle.plugin.jdeps'
implementationClass = 'org.xbib.gradle.plugin.jdeps.JDepsPlugin'
version = project.version
description = 'Gradle JDeps plugin'
displayName = 'Gradle JDeps plugin'
tags.set(['jdeps'])
}
}
}
}

@ -0,0 +1,38 @@
package org.xbib.gradle.plugin.jdeps
import org.gradle.api.Action
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.plugins.BasePlugin
import org.gradle.api.plugins.JavaPlugin
import org.gradle.api.plugins.JavaPluginConvention
import org.gradle.api.tasks.TaskProvider
import org.gradle.api.tasks.compile.JavaCompile
class JDepsPlugin implements Plugin<Project> {
void apply(Project project) {
project.plugins.apply(JavaPlugin)
TaskProvider<JDepsReportTask> report = project.tasks.register('jdepsReport', JDepsReportTask,
new Action<JDepsReportTask>() {
@Override
void execute(JDepsReportTask t) {
t.dependsOn(project.tasks.named('classes'))
t.group = BasePlugin.BUILD_GROUP
t.description = 'Generate a jdeps report on project classes and dependencies'
t.compileJava = project.tasks.named(JavaPlugin.COMPILE_JAVA_TASK_NAME, JavaCompile)
t.javaPluginConvention.set(project.convention.getPlugin(JavaPluginConvention))
t.projectName.set(project.name)
t.reportDir.convention(project.layout.buildDirectory.dir('reports/jdeps'))
t.projectConfigurations = project.configurations
}
})
project.tasks.named('check').configure(new Action<Task>() {
@Override
void execute(Task t) {
t.dependsOn(report)
}
})
}
}

@ -0,0 +1,674 @@
package org.xbib.gradle.plugin.jdeps
import org.gradle.api.DefaultTask
import org.gradle.api.JavaVersion
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.ConfigurationContainer
import org.gradle.api.file.Directory
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.model.ObjectFactory
import org.gradle.api.plugins.JavaPluginConvention
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.MapProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.SourceSet
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.TaskProvider
import org.gradle.api.tasks.compile.JavaCompile
import org.gradle.api.tasks.options.Option
import org.gradle.process.ExecOperations
import org.xbib.gradle.plugin.jdeps.util.BooleanState
import org.xbib.gradle.plugin.jdeps.util.DirectoryState
import org.xbib.gradle.plugin.jdeps.util.ListState
import org.xbib.gradle.plugin.jdeps.util.MapState
import org.xbib.gradle.plugin.jdeps.util.SimpleBooleanState
import org.xbib.gradle.plugin.jdeps.util.SimpleDirectoryState
import org.xbib.gradle.plugin.jdeps.util.SimpleListState
import org.xbib.gradle.plugin.jdeps.util.SimpleMapState
import org.xbib.gradle.plugin.jdeps.util.SimpleStringState
import org.xbib.gradle.plugin.jdeps.util.StringState
import javax.inject.Inject
import java.util.regex.Pattern
class JDepsReportTask extends DefaultTask {
private static final Pattern WARNING = Pattern.compile("^(?:Warning:.*)|(?:.+->\\s([a-zA-Z\\.]+)\\s+JDK internal API.*)")
private static final Pattern ERROR = Pattern.compile("^(?:Error:.*)|Exception in thread.*")
@Inject
protected ExecOperations execOperations;
private final BooleanState listDeps
private final BooleanState listReducedDeps
private final BooleanState printModuleDeps
private final BooleanState verbose
private final BooleanState modular
private final BooleanState summary
private final BooleanState profile
private final BooleanState recursive
private final BooleanState jdkinternals
private final BooleanState consoleOutput
private final BooleanState apionly
private final BooleanState failOnWarning
private final BooleanState missingDeps
private final BooleanState ignoreMissingDeps
private final ListState pkgs
private final ListState requires
private final StringState include
private final StringState regex
private final StringState filter
private final ListState configurations
private final ListState classpaths
private final ListState sourceSets
private final StringState multiRelease
private final MapState multiReleaseJars
private final DirectoryState dotOutput
@OutputDirectory
final DirectoryProperty reportDir
@Internal
TaskProvider<JavaCompile> compileJava
@Internal
final Property<JavaPluginConvention> javaPluginConvention
@Internal
final Property<String> projectName
@Internal
ConfigurationContainer projectConfigurations
@Inject
JDepsReportTask(ObjectFactory objects) {
extensions.create('moduleOptions', ModuleOptions)
reportDir = objects.directoryProperty()
projectName = objects.property(String)
javaPluginConvention = objects.property(JavaPluginConvention)
listDeps = SimpleBooleanState.of(this, 'jdeps.list.deps', false)
listReducedDeps = SimpleBooleanState.of(this, 'jdeps.list.reduced.deps', false)
printModuleDeps = SimpleBooleanState.of(this, 'jdeps.print.module.deps', false)
verbose = SimpleBooleanState.of(this, 'jdeps.verbose', false)
modular = SimpleBooleanState.of(this, 'jdeps.modular', false)
summary = SimpleBooleanState.of(this, 'jdeps.summary', false)
profile = SimpleBooleanState.of(this, 'jdeps.profile', false)
recursive = SimpleBooleanState.of(this, 'jdeps.recursive', false)
jdkinternals = SimpleBooleanState.of(this, 'jdeps.jdkinternals', false)
consoleOutput = SimpleBooleanState.of(this, 'jdeps.console.output', true)
apionly = SimpleBooleanState.of(this, 'jdeps.apionly', false)
failOnWarning = SimpleBooleanState.of(this, 'jdeps.fail.on.warning', false)
missingDeps = SimpleBooleanState.of(this, 'jdeps.missing.deps', false)
ignoreMissingDeps = SimpleBooleanState.of(this, 'jdeps.ignore.missing.deps', false)
include = SimpleStringState.of(this, 'jdeps.include', "")
regex = SimpleStringState.of(this, 'jdeps.regex', "")
filter = SimpleStringState.of(this, 'jdeps.filter', "")
pkgs = SimpleListState.of(this, 'jdeps.package', [])
requires = SimpleListState.of(this, 'jdeps.require', [])
configurations = SimpleListState.of(this, 'jdeps.configurations', [])
classpaths = SimpleListState.of(this, 'jdeps.classpaths', [])
sourceSets = SimpleListState.of(this, 'jdeps.sourcesets', ['main'])
multiRelease = SimpleStringState.of(this, 'jdeps.multi.release', 'base')
multiReleaseJars = SimpleMapState.of(this, 'jdeps.multi.release.jars', [:])
dotOutput = SimpleDirectoryState.of(this, 'jdeps.dot.output', (Directory) null)
}
@Option(option = 'list-deps', description = 'Lists the module dependences')
void setListDeps(boolean value) { listDeps.property.set(value) }
@Option(option = 'list-reduced-deps', description = 'Lists the module dependences')
void setListReducedDeps(boolean value) { listReducedDeps.property.set(value) }
@Option(option = 'print-module-deps', description = 'Comma-separated list of module dependences')
void setPrintModuleDeps(boolean value) { printModuleDeps.property.set(value) }
@Option(option = 'verbose', description = 'Print all class level dependences')
void setVerbose(boolean value) { verbose.property.set(value) }
@Option(option = 'modular', description = 'Uses the module path instead of the classpath')
void setModular(boolean value) { modular.property.set(value) }
@Option(option = 'summary', description = 'Print dependency summary only')
void setSummary(boolean value) { summary.property.set(value) }
@Option(option = 'profile', description = 'Show profile containing a package')
void setProfile(boolean value) { profile.property.set(value) }
@Option(option = 'recursive', description = 'Recursively traverse all run-time dependences')
void setRecursive(boolean value) { recursive.property.set(value) }
@Option(option = 'jdkinternals', description = 'Finds class-level dependences on JDK internal APIs')
void setJdkinternals(boolean value) { jdkinternals.property.set(value) }
@Option(option = 'console-output', description = 'Print out report to console')
void setConsoleOutput(boolean value) { consoleOutput.property.set(value) }
@Option(option = 'apionly', description = 'Restrict analysis to APIs')
void setApionly(boolean value) { apionly.property.set(value) }
@Option(option = 'fail-on-warning', description = 'Fails the build if jdeps finds any warnings')
void setFailOnWarning(boolean value) { failOnWarning.property.set(value) }
@Option(option = 'missing-deps', description = 'Finds missing dependences')
void setMissingDeps(boolean value) { missingDeps.property.set(value) }
@Option(option = 'ignore-missing-deps', description = 'Ignore missing dependences')
void setIgnoreMissingDeps(boolean value) { ignoreMissingDeps.property.set(value) }
@Option(option = 'include', description = 'Restrict analysis to classes matching pattern')
void setInclude(String value) { include.property.set(value) }
@Option(option = 'regex', description = 'Finds dependences matching the given pattern')
void setRegex(String value) { regex.property.set(value) }
@Option(option = 'filter', description = 'Filter dependences matching the given pattern')
void setFilter(String value) { filter.property.set(value) }
@Option(option = 'package', description = 'Finds dependences matching the given package name. REPEATABLE')
void setPackage(String value) { pkgs.property.add(value) }
@Option(option = 'require', description = 'Finds dependences matching the given module name. REPEATABLE')
void setRequire(String value) { requires.property.add(value) }
@Option(option = 'configurations', description = 'Configurations to be analyzed')
void setConfigurations(String value) { configurations.property.set(value.split(',').toList()) }
@Option(option = 'classpaths', description = 'Classpaths to be analyzed')
void setClasspaths(String value) { classpaths.property.set(value.split(',').toList()) }
@Option(option = 'sourcesets', description = 'SourceSets to be analyzed')
void setSourceSets(String value) { sourceSets.property.set(value.split(',').toList()) }
@Option(option = 'multi-release', description = 'Set the multi-release level')
void setMultiRelease(String value) { multiRelease.property.set(value) }
@Option(option = 'dot-output', description = 'Destination directory for DOT file output')
void setDotOutput(String value) { dotOutput.property.set(new File(value)) }
@Internal
Property<Boolean> getListDeps() { listDeps.property }
@Input
Provider<Boolean> getResolvedListDeps() { listDeps.provider }
@Internal
Property<Boolean> getListReducedDeps() { listReducedDeps.property }
@Input
Provider<Boolean> getResolvedListReducedDeps() { listReducedDeps.provider }
@Internal
Property<Boolean> getPrintModuleDeps() { printModuleDeps.property }
@Input
Provider<Boolean> getResolvedPrintModuleDeps() { printModuleDeps.provider }
@Internal
Property<Boolean> getVerbose() { verbose.property }
@Input
Provider<Boolean> getResolvedVerbose() { verbose.provider }
@Internal
Property<Boolean> getModular() { modular.property }
@Input
Provider<Boolean> getResolvedModular() { modular.provider }
@Internal
Property<Boolean> getSummary() { summary.property }
@Input
Provider<Boolean> getResolvedSummary() { summary.provider }
@Internal
Property<Boolean> getProfile() { profile.property }
@Input
Provider<Boolean> getResolvedProfile() { profile.provider }
@Internal
Property<Boolean> getRecursive() { recursive.property }
@Input
Provider<Boolean> getResolvedRecursive() { recursive.provider }
@Internal
Property<Boolean> getJdkinternals() { jdkinternals.property }
@Input
Provider<Boolean> getResolvedJdkinternals() { jdkinternals.provider }
@Internal
Property<Boolean> getConsoleOutput() { consoleOutput.property }
@Input
Provider<Boolean> getResolvedConsoleOutput() { consoleOutput.provider }
@Internal
Property<Boolean> getApionly() { apionly.property }
@Input
Provider<Boolean> getResolvedApionly() { apionly.provider }
@Internal
Property<Boolean> getFailOnWarning() { failOnWarning.property }
@Input
Provider<Boolean> getResolvedFailOnWarning() { failOnWarning.provider }
@Internal
Property<Boolean> getMissingDeps() { missingDeps.property }
@Input
Provider<Boolean> getResolvedMissingDeps() { missingDeps.provider }
@Internal
Property<Boolean> getIgnoreMissingDeps() { ignoreMissingDeps.property }
@Input
Provider<Boolean> getResolvedIgnoreMissingDeps() { ignoreMissingDeps.provider }
@Internal
Property<String> getInclude() { include.property }
@Input
@Optional
Provider<String> getResolvedInclude() { include.provider }
@Internal
Property<String> getRegex() { regex.property }
@Input
@Optional
Provider<String> getResolvedRegex() { regex.provider }
@Internal
Property<String> getFilter() { filter.property }
@Input
@Optional
Provider<String> getResolvedFilter() { filter.provider }
@Internal
ListProperty<String> getPackages() { pkgs.property }
@Input
@Optional
Provider<List<String>> getResolvedPackages() { pkgs.provider }
@Internal
ListProperty<String> getRequires() { requires.property }
@Input
@Optional
Provider<List<String>> getResolvedRequires() { requires.provider }
@Internal
ListProperty<String> getConfigurations() { configurations.property }
@Input
@Optional
Provider<List<String>> getResolvedConfigurations() { configurations.provider }
@Internal
ListProperty<String> getClasspaths() { classpaths.property }
@Input
@Optional
Provider<List<String>> getResolvedClasspaths() { classpaths.provider }
@Internal
ListProperty<String> getSourceSets() { sourceSets.property }
@Input
@Optional
Provider<List<String>> getResolvedSourceSets() { sourceSets.provider }
@Internal
Property<String> getMultiRelease() { multiRelease.property }
@Input
Provider<String> getResolvedMultiRelease() { multiRelease.provider }
@Internal
MapProperty<String, String> getMultiReleaseJars() { multiReleaseJars.property }
@Input
@Optional
Provider<Map<String, String>> getResolvedMultiReleaseJars() { multiReleaseJars.provider }
@Internal
DirectoryProperty getDotOutput() { dotOutput.property }
@InputDirectory
@Optional
Provider<Directory> getResolvedDotOutput() { dotOutput.provider }
@TaskAction
void evaluate() {
ModuleOptions moduleOptions = extensions.getByType(ModuleOptions)
String classpath = compileJava.get().classpath.asPath
List<String> compilerArgs = compileJava.get().options.compilerArgs
List<String> commandOutput = []
int explicitCommand = 0
if (resolvedListDeps.get()) {
explicitCommand++
}
if (resolvedListReducedDeps.get()) {
explicitCommand++
}
if (resolvedPrintModuleDeps.get()) {
explicitCommand++
}
if (explicitCommand > 1) {
throw new IllegalArgumentException("--list-deps, --list-reduced-deps, --print-module-deps are mutually exclusive")
}
final List<String> baseCmd = ['jdeps']
if (resolvedSummary.get()) {
if (resolvedMissingDeps.get()) {
throw new IllegalArgumentException("-s, --missing-deps are mutually exclusive")
}
baseCmd << '-s'
}
if (resolvedVerbose.get()) {
baseCmd << '-v'
}
if (resolvedProfile.get()) {
baseCmd << '-P'
}
if (resolvedRecursive.get()) {
baseCmd << '-R'
}
if (resolvedJdkinternals.get()) {
baseCmd << '-jdkinternals'
}
if (resolvedApionly.get()) {
baseCmd << '-apionly'
}
if (getResolvedDotOutput().present) {
baseCmd << '-dotoutput'
baseCmd << resolvedDotOutput.get().asFile.absolutePath
resolvedDotOutput.get().asFile.mkdirs()
}
if (resolvedIgnoreMissingDeps.get()) {
baseCmd << '--ignore-missing-deps'
}
if (JavaVersion.current().java9Compatible) {
if (resolvedMultiRelease.present) {
baseCmd << '--multi-release'
baseCmd << resolvedMultiRelease.get()
}
if (resolvedModular.get()) {
int modulePathIndex = compilerArgs.indexOf('--module-path')
if (modulePathIndex > -1) {
baseCmd << '--module-path'
baseCmd << compilerArgs[modulePathIndex + 1]
} else {
baseCmd << '--module-path'
baseCmd << classpath
}
} else if (classpath) {
baseCmd << '--class-path'
baseCmd << classpath
} else {
int modulePathIndex = compilerArgs.indexOf('--module-path')
if (modulePathIndex > -1) {
baseCmd << '--module-path'
baseCmd << compilerArgs[modulePathIndex + 1]
}
}
if (!moduleOptions.addModules.empty) {
baseCmd << '--add-modules'
baseCmd << moduleOptions.addModules.join(',')
} else {
int addModulesIndex = compilerArgs.indexOf('--add-modules')
if (addModulesIndex > -1) {
baseCmd << '--add-modules'
baseCmd << compilerArgs[addModulesIndex + 1]
}
}
List<String> requires = resolvedRequires.get()
List<String> packages = resolvedPackages.get()
String regex = resolvedRegex.orNull
int exclusive = 0
if (!requires.isEmpty()) {
exclusive++
}
if (!packages.isEmpty()) {
exclusive++
}
if (regex) {
exclusive++
}
if (exclusive > 1) {
throw new IllegalArgumentException("--package, --regex, --require are mutually exclusive")
}
if (resolvedMissingDeps.get()) {
exclusive = 1
if (!packages.isEmpty()) {
exclusive++
}
if (regex) {
exclusive++
}
if (exclusive > 1) {
throw new IllegalArgumentException("--package, --regex, --missing-deps are mutually exclusive")
}
}
requires.each { s ->
baseCmd << '--require'
baseCmd << s
}
packages.each { s ->
baseCmd << '--package'
baseCmd << s
}
if (regex) {
baseCmd << '--regex'
baseCmd << regex
}
if (resolvedFilter.get()) {
String filter = resolvedFilter.get()
if (filter in [':package', ':archive', ':module', ':none']) {
baseCmd << '-filter' + filter
} else {
baseCmd << '-filter'
baseCmd << filter
}
}
}
// compileJava.get().classpath = project.files()
//logger.info("jdeps version is ${executeCommand(['jdeps', '-version'])}")
//Map<String, String> outputs = [:]
resolvedSourceSets.get().each { sc ->
SourceSet sourceSet = javaPluginConvention.get().sourceSets.findByName(sc)
logger.info("Running jdeps on sourceSet ${sourceSet.name}")
sourceSet.output.files.each { File file ->
if (!file.exists()) {
return // skip
}
List<String> cmd = applyExplicitCommand(baseCmd)
logger.info("jdeps command set to ${cmd.join(' ')}")
executeCommandOn(cmd, file.absolutePath)
/*if (output) {
commandOutput << "\nProject: ${projectName.get()}\n${output}".toString()
outputs[sourceSet.name] = output
}*/
List<String> warnings = getWarnings(output)
if (warnings && getResolvedFailOnWarning().get()) {
throw new IllegalStateException("jdeps reported warnings: " +
System.lineSeparator() +
warnings.join(System.lineSeparator()))
}
List<String> errors = getErrors(output)
if (errors) {
throw new IllegalStateException("jdeps reported errors: " +
System.lineSeparator() +
errors.join(System.lineSeparator()))
}
}
}
for (String c : resolvedConfigurations.get()) {
inspectConfiguration(projectConfigurations[c.trim()], baseCmd)
}
for (String c : resolvedClasspaths.get()) {
inspectConfiguration(projectConfigurations[c.trim()], baseCmd)
}
/*if (commandOutput) {
commandOutput = commandOutput.unique()
if (resolvedConsoleOutput.get()) {
println commandOutput.join('\n')
}
File parentFile = reportDir.get().asFile
if (!parentFile.exists()) {
parentFile.mkdirs()
}
File logFile = new File(parentFile, 'jdeps-report.txt')
logFile.append(commandOutput.join('\n'))
String prefix = 'jdeps-'
if (resolvedListDeps.get()) {
prefix = 'list-deps-'
} else if (resolvedListReducedDeps.get()) {
prefix = 'list-reduced-deps-'
} else if (resolvedPrintModuleDeps.get()) {
prefix = 'print-module-deps-'
}
outputs.each { k, v ->
logFile = new File(parentFile, prefix + k + '.txt')
logFile.append(v)
}
}*/
}
private void inspectConfiguration(Configuration configuration,
List<String> baseCmd) {
logger.info("Running jdeps on configuration ${configuration.name}")
configuration.resolve().each { File file ->
if (!file.exists()) {
return // skip
}
List<String> command = new ArrayList<>(baseCmd)
if (JavaVersion.current().java9Compatible) {
String multiReleaseVersion = resolveMultiReleaseVersion(file.name, resolvedMultiReleaseJars.get())
if (multiReleaseVersion) {
command.add(1, multiReleaseVersion)
command.add(1, '--multi-release')
}
}
command = applyExplicitCommand(command)
logger.info("jdeps command set to: ${command.join(' ')} ${file.absolutePath}")
executeCommandOn(command, file.absolutePath)
//if (output) {
//commandOutput << "\nDependency: ${file.name}\n${output}".toString()
//outputs[configuration.name] = output
//}
/*List<String> warnings = getWarnings(output)
if (warnings && getResolvedFailOnWarning().get()) {
throw new IllegalStateException("jdeps reported errors/warnings: " +
System.lineSeparator() +
warnings.join(System.lineSeparator()))
}*/
}
}
private List<String> applyExplicitCommand(List<String> cmd) {
List<String> c = new ArrayList<>(cmd)
String subcommand = ''
if (resolvedListDeps.get()) {
subcommand = '--list-deps'
} else if (resolvedListReducedDeps.get()) {
subcommand = '--list-reduced-deps'
} else if (resolvedPrintModuleDeps.get()) {
subcommand = '--print-module-deps'
}
if (subcommand) {
if (c.contains('--class-path')) {
c.add(c.indexOf('--class-path'), subcommand)
} else if (c.contains('--module-path')) {
c.add(c.indexOf('--module-path'), subcommand)
} else {
c << subcommand
}
}
c
}
private String executeCommandOn(List<String> baseCmd, String path) {
List<String> cmd = []
cmd.addAll(baseCmd)
cmd.add(path)
return executeCommand(cmd)
}
private String executeCommand(List<String> cmd) {
ByteArrayOutputStream out = new ByteArrayOutputStream()
execOperations.exec(e -> {
e.setErrorOutput(out)
e.commandLine(cmd)
})
return out.toString().trim()
}
private static String resolveMultiReleaseVersion(String artifactName, Map<String, String> multiReleaseJars) {
for (Map.Entry<String, String> e : multiReleaseJars.entrySet()) {
if (artifactName.matches(e.key.trim())) {
return e.value.trim()
}
}
null
}
private static List<String> getWarnings(String output) {
List<String> warnings = []
output.eachLine { String line ->
if (WARNING.matcher(line).matches()) {
warnings.add(line)
}
}
warnings
}
private static List<String> getErrors(String output) {
List<String> errors = []
output.eachLine { String line ->
if (ERROR.matcher(line).matches()) {
errors.add(line)
}
}
errors
}
}

@ -0,0 +1,9 @@
package org.xbib.gradle.plugin.jdeps
import groovy.transform.Canonical
@Canonical
class ModuleOptions {
List<String> addModules = []
}

@ -0,0 +1,13 @@
package org.xbib.gradle.plugin.jdeps.util
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
interface BooleanState {
Property<Boolean> getProperty()
Provider<Boolean> getProvider()
boolean getValue()
}

@ -0,0 +1,14 @@
package org.xbib.gradle.plugin.jdeps.util
import org.gradle.api.file.Directory
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.provider.Provider
interface DirectoryState {
DirectoryProperty getProperty()
Provider<Directory> getProvider()
Directory getValue()
}

@ -0,0 +1,13 @@
package org.xbib.gradle.plugin.jdeps.util
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Provider
interface ListState {
ListProperty<String> getProperty()
Provider<List<String>> getProvider()
List<String> getValue()
}

@ -0,0 +1,13 @@
package org.xbib.gradle.plugin.jdeps.util;
import org.gradle.api.provider.MapProperty
import org.gradle.api.provider.Provider
interface MapState {
MapProperty<String, String> getProperty()
Provider<Map<String, String>> getProvider()
Map<String, String> getValue()
}

@ -0,0 +1,100 @@
package org.xbib.gradle.plugin.jdeps.util
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.internal.provider.Providers
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.api.provider.ProviderFactory
import static java.util.Objects.requireNonNull
final class SimpleBooleanState implements BooleanState {
final Property<Boolean> property
final Provider<Boolean> provider
private final Project project
SimpleBooleanState(Project project,
Property<Boolean> property,
Provider<Boolean> provider) {
this.project = requireNonNull(project, "Argument 'project' must not be null")
this.property = requireNonNull(property, "Argument 'property' must not be null")
this.provider = requireNonNull(provider, "Argument 'provider' must not be null")
}
@Override
boolean getValue() {
booleanProvider(project.providers, property, provider, false).get()
}
static SimpleBooleanState of(Task task,
String key,
boolean defaultValue) {
requireNonNull(task, "Argument 'task' must not be null")
Project project = task.project
Property<Boolean> property = project.objects.property(Boolean).convention(Providers.<Boolean>notDefined())
Provider<Boolean> provider = booleanProvider(key, property, project, defaultValue)
new SimpleBooleanState(project, property, provider)
}
static Provider<Boolean> booleanProvider(ProviderFactory providers,
Property<Boolean> property,
Provider<Boolean> provider,
boolean defaultValue) {
providers.provider {
return provider ? provider.getOrElse(property.getOrElse(defaultValue)) : property.getOrElse(defaultValue)
}
}
static Provider<Boolean> booleanProvider(String key,
Property<Boolean> property,
Project project,
boolean defaultValue) {
booleanProvider(toEnv(key), toProperty(key), property, project, defaultValue)
}
static Provider<Boolean> booleanProvider(String envKey,
String propertyKey,
Property<Boolean> property,
Project project,
boolean defaultValue) {
project.providers.provider {
String value = resolveValue(envKey, propertyKey, project)
return !isBlank(value) ? Boolean.parseBoolean(value) : property.getOrElse(defaultValue)
}
}
private static String resolveValue(String envKey, String propertyKey, Project project) {
String value = System.getenv(envKey)
if (isBlank(value)) {
value = System.getProperty(propertyKey)
}
if (isBlank(value)) {
value = project.findProperty(propertyKey) as String
}
return value
}
private static boolean isBlank(String str) {
if (str == null || str.length() == 0) {
return true
}
for (char c : str.toCharArray()) {
if (!Character.isWhitespace(c)) {
return false
}
}
return true
}
private static String toEnv(String key) {
key.toUpperCase().replace('.', '_')
}
private static String toProperty(String key) {
key.uncapitalize().replace('_', '.')
}
}

@ -0,0 +1,107 @@
package org.xbib.gradle.plugin.jdeps.util
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.file.Directory
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.internal.provider.Providers
import org.gradle.api.provider.Provider
import org.gradle.api.provider.ProviderFactory
import java.nio.file.Paths
import static java.util.Objects.requireNonNull
final class SimpleDirectoryState implements DirectoryState {
final DirectoryProperty property
final Provider<Directory> provider
private final Project project
SimpleDirectoryState(Project project,
DirectoryProperty property,
Provider<Directory> provider) {
this.project = requireNonNull(project, "Argument 'project' must not be null")
this.property = requireNonNull(property, "Argument 'property' must not be null")
this.provider = requireNonNull(provider, "Argument 'provider' must not be null")
}
@Override
Directory getValue() {
directoryProvider(project.providers, property, provider, (Directory) null).get()
}
static SimpleDirectoryState of(Task task, String key, Directory defaultValue) {
requireNonNull(task, "Argument 'task' must not be null")
Project project = task.project
DirectoryProperty property = project.objects.directoryProperty().convention(Providers.<Directory>notDefined())
Provider<Directory> provider = directoryProvider(key, property, project, defaultValue)
new SimpleDirectoryState(project, property, provider)
}
static Provider<Directory> directoryProvider(ProviderFactory providers,
DirectoryProperty property,
Provider<Directory> provider,
Directory defaultValue) {
providers.provider {
return provider ? provider.getOrElse(property.getOrElse(defaultValue)) : property.getOrElse(defaultValue)
}
}
static Provider<Directory> directoryProvider(String key,
DirectoryProperty property,
Project project,
Directory defaultValue) {
directoryProvider(toEnv(key), toProperty(key), property, project, defaultValue)
}
static Provider<Directory> directoryProvider(String envKey,
String propertyKey,
DirectoryProperty property,
Project project,
Directory defaultValue) {
project.providers.provider {
String value = resolveValue(envKey, propertyKey, project)
if (!isBlank(value)) {
DirectoryProperty p = project.objects.directoryProperty()
p.set(Paths.get(value).toFile())
return p.get()
}
return property.getOrElse(defaultValue)
}
}
private static String resolveValue(String envKey, String propertyKey, Project project) {
String value = System.getenv(envKey)
if (isBlank(value)) {
value = System.getProperty(propertyKey)
}
if (isBlank(value)) {
value = project.findProperty(propertyKey) as String
}
return value
}
private static boolean isBlank(String str) {
if (str == null || str.length() == 0) {
return true
}
for (char c : str.toCharArray()) {
if (!Character.isWhitespace(c)) {
return false
}
}
return true
}
private static String toEnv(String key) {
key.toUpperCase().replace('.', '_')
}
private static String toProperty(String key) {
key.uncapitalize().replace('_', '.')
}
}

@ -0,0 +1,108 @@
package org.xbib.gradle.plugin.jdeps.util
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.internal.provider.Providers
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Provider
import org.gradle.api.provider.ProviderFactory
import static java.util.Objects.requireNonNull
final class SimpleListState implements ListState {
final ListProperty<String> property
final Provider<List<String>> provider
private final Project project
SimpleListState(Project project,
ListProperty<String> property,
Provider<List<String>> provider) {
this.project = requireNonNull(project, "Argument 'project' must not be null")
this.property = requireNonNull(property, "Argument 'property' must not be null")
this.provider = requireNonNull(provider, "Argument 'provider' must not be null")
}
@Override
List<String> getValue() {
listProvider(project.providers, property, provider, Collections.<String> emptyList()).get()
}
static SimpleListState of(Task task, String key, List<String> defaultValue) {
requireNonNull(task, "Argument 'task' must not be null")
Project project = task.project
ListProperty<String> property = project.objects.listProperty(String).convention(Providers.<List<String>>notDefined())
Provider<List<String>> provider = listProvider(key, property, project, defaultValue)
new SimpleListState(project, property, provider)
}
static Provider<List<String>> listProvider(ProviderFactory providers,
ListProperty<String> property,
Provider<List<String>> provider,
List<String> defaultValue) {
providers.provider {
return provider ? provider.getOrElse(property.getOrElse(defaultValue)) : property.getOrElse(defaultValue)
}
}
static Provider<List<String>> listProvider(String key,
ListProperty<String> property,
Project project,
List<String> defaultValue) {
listProvider(toEnv(key), toProperty(key), property, project, defaultValue)
}
static Provider<List<String>> listProvider(String envKey,
String propertyKey,
ListProperty<String> property,
Project project,
List<String> defaultValue) {
project.providers.provider {
if (property.present) {
return property.get()
}
String value = resolveValue(envKey, propertyKey, project)
if (!isBlank(value)) {
List<String> list = new ArrayList<>()
for (String v : value.split(',')) {
if (!isBlank(v)) list.add(v.trim())
}
return list
}
defaultValue
}
}
private static String resolveValue(String envKey, String propertyKey, Project project) {
String value = System.getenv(envKey)
if (isBlank(value)) {
value = System.getProperty(propertyKey)
}
if (isBlank(value)) {
value = project.findProperty(propertyKey) as String
}
return value
}
private static boolean isBlank(String str) {
if (str == null || str.length() == 0) {
return true
}
for (char c : str.toCharArray()) {
if (!Character.isWhitespace(c)) {
return false
}
}
return true
}
private static String toEnv(String key) {
key.toUpperCase().replace('.', '_')
}
private static String toProperty(String key) {
key.uncapitalize().replace('_', '.')
}
}

@ -0,0 +1,115 @@
package org.xbib.gradle.plugin.jdeps.util
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.internal.provider.Providers
import org.gradle.api.provider.MapProperty
import org.gradle.api.provider.Provider
import org.gradle.api.provider.ProviderFactory
import static java.util.Objects.requireNonNull
final class SimpleMapState implements MapState {
final MapProperty<String, String> property
final Provider<Map<String, String>> provider
private final Project project
SimpleMapState(Project project,
MapProperty<String, String> property,
Provider<Map<String, String>> provider) {
this.project = requireNonNull(project, "Argument 'project' must not be null")
this.property = requireNonNull(property, "Argument 'property' must not be null")
this.provider = requireNonNull(provider, "Argument 'provider' must not be null")
}
@Override
Map<String, String> getValue() {
mapProvider(project.providers, property, provider, Collections.<String, String> emptyMap()).get()
}
static SimpleMapState of(Task task, String key, Map<String, String> defaultValue) {
requireNonNull(task, "Argument 'task' must not be null")
Project project = task.project
MapProperty<String, String> property = project.objects.mapProperty(String, String).convention(Providers.<Map<String, String>>notDefined())
Provider<Map<String, String>> provider = mapProvider(key, property, project, defaultValue)
new SimpleMapState(project, property, provider)
}
static <K, V> Provider<Map<K, V>> mapProvider(ProviderFactory providers,
MapProperty<K, V> property,
Provider<Map<K, V>> provider,
Map<K, V> defaultValue) {
providers.provider {
Map<K, V> map = new LinkedHashMap<>(defaultValue)
if (property.present) {
map.putAll(property.get())
}
if (provider) {
map.putAll(provider.get())
}
return map
}
}
static <K,V> Provider<Map<K, V>> mapProvider(String key,
MapProperty<K, V> property,
Project project,
Map<K, V> defaultValue) {
mapProvider(toEnv(key), toProperty(key), property, project, defaultValue)
}
static <K, V> Provider<Map<K, V>> mapProvider(String envKey,
String propertyKey,
MapProperty<K, V> property,
Project project,
Map<K, V> defaultValue) {
project.providers.provider {
String value = resolveValue(envKey, propertyKey, project)
if (i!sBlank(value)) {
Map<K, V> map = new LinkedHashMap<>()
for (String val : value.split(',')) {
String[] kv = val.split('=')
if (kv.length == 2) {
map.put(kv[0] as K, kv[1] as V)
}
}
return map
}
return property.orElse(defaultValue).get()
}
}
private static String resolveValue(String envKey, String propertyKey, Project project) {
String value = System.getenv(envKey)
if (isBlank(value)) {
value = System.getProperty(propertyKey)
}
if (isBlank(value)) {
value = project.findProperty(propertyKey) as String
}
return value
}
private static boolean isBlank(String str) {
if (str == null || str.length() == 0) {
return true
}
for (char c : str.toCharArray()) {
if (!Character.isWhitespace(c)) {
return false
}
}
return true
}
private static String toEnv(String key) {
key.toUpperCase().replace('.', '_')
}
private static String toProperty(String key) {
key.uncapitalize().replace('_', '.')
}
}

@ -0,0 +1,98 @@
package org.xbib.gradle.plugin.jdeps.util
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.internal.provider.Providers
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.api.provider.ProviderFactory
import static java.util.Objects.requireNonNull
final class SimpleStringState implements StringState {
final Property<String> property
final Provider<String> provider
private final Project project
@Override
String getValue() {
stringProvider(project.providers, property, provider, '').get()
}
SimpleStringState(Project project,
Property<String> property,
Provider<String> provider) {
this.project = requireNonNull(project, "Argument 'project' must not be null")
this.property = requireNonNull(property, "Argument 'property' must not be null")
this.provider = requireNonNull(provider, "Argument 'provider' must not be null")
}
static SimpleStringState of(Task task, String key, String defaultValue) {
requireNonNull(task, "Argument 'task' must not be null")
Project project = task.project
Property<String> property = project.objects.property(String).convention(Providers.<String>notDefined())
Provider<String> provider = stringProvider(key, property, project, defaultValue)
new SimpleStringState(project, property, provider)
}
static Provider<String> stringProvider(ProviderFactory providers,
Property<String> property,
Provider<String> provider,
String defaultValue) {
providers.provider {
return provider ? provider.getOrElse(property.getOrElse(defaultValue)) : property.getOrElse(defaultValue)
}
}
static Provider<String> stringProvider(String key,
Property<String> property,
Project project,
String defaultValue) {
stringProvider(toEnv(key), toProperty(key), property, project, defaultValue)
}
static Provider<String> stringProvider(String envKey,
String propertyKey,
Property<String> property,
Project project,
String defaultValue) {
project.providers.provider {
String value = resolveValue(envKey, propertyKey, project)
return !isBlank(value) ? value : property.getOrElse(defaultValue)
}
}
private static String resolveValue(String envKey, String propertyKey, Project project) {
String value = System.getenv(envKey)
if (isBlank(value)) {
value = System.getProperty(propertyKey)
}
if (isBlank(value)) {
value = project.findProperty(propertyKey) as String
}
return value
}
private static boolean isBlank(String str) {
if (str == null || str.length() == 0) {
return true
}
for (char c : str.toCharArray()) {
if (!Character.isWhitespace(c)) {
return false
}
}
return true
}
private static String toEnv(String key) {
key.toUpperCase().replace('.', '_')
}
private static String toProperty(String key) {
key.uncapitalize().replace('_', '.')
}
}

@ -0,0 +1,13 @@
package org.xbib.gradle.plugin.jdeps.util
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
interface StringState {
Property<String> getProperty()
Provider<String> getProvider()
String getValue()
}

@ -15,26 +15,18 @@ dependencies {
testImplementation gradleTestKit()
}
gradlePlugin {
plugins {
jflexPlugin {
id = 'org.xbib.gradle.plugin.jflex'
implementationClass = 'org.xbib.gradle.plugin.jflex.JFlexPlugin'
}
}
}
if (project.hasProperty('gradle.publish.key')) {
pluginBundle {
website = scmUrl
vcsUrl = scmUrl
gradlePlugin {
website = 'https://xbib.org/joerg/gradle-plugins/src/branch/main/gradle-plugin-jflex'
vcsUrl = 'https://xbib.org/joerg/gradle-plugins'
plugins {
jflexPlugin {
id = 'org.xbib.gradle.plugin.jflex'
implementationClass = 'org.xbib.gradle.plugin.jflex.JFlexPlugin'
version = project.version
description = 'Gradle JFlex plugin'
displayName = 'Gradle JFlex plugin'
tags = ['jflex']
tags.set(['jflex'])
}
}
}

@ -1 +1,2 @@
version = 1.6.0
name = gradle-plugin-jflex
version = 3.1.0

@ -46,7 +46,6 @@ class JFlexTask extends DefaultTask {
Options.encoding = ext.encoding ? Charset.forName(ext.encoding) : Charset.defaultCharset()
Options.verbose = ext.verbose
Options.progress = ext.progress
Options.unused_warning = ext.unused_warning
Options.jlex = ext.jlex
Options.no_minimize = ext.no_minimize
Options.no_backup = ext.no_backup

@ -0,0 +1,32 @@
plugins {
id 'java-gradle-plugin'
alias(libs.plugins.publish)
}
apply plugin: 'java-gradle-plugin'
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-jlink'
vcsUrl = 'https://xbib.org/joerg/gradle-plugins'
plugins {
jlinkPlugin {
id = 'org.xbib.gradle.plugin.jlink'
implementationClass = 'org.xbib.gradle.plugin.jlink.JlinkPlugin'
version = project.version
description = 'Gradle Jlink plugin'
displayName = 'Gradle Jlink plugin'
tags.set(['jlink'])
}
}
}
}

@ -0,0 +1,9 @@
This work is based on
https://github.com/gradlex-org/java-module-packaging
by Jendrik Johannes
as of Feb 11, 2024
License: Apache 2.0

@ -0,0 +1,34 @@
plugins {
id 'java-gradle-plugin'
alias(libs.plugins.publish)
}
apply plugin: 'java-gradle-plugin'
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()
testImplementation testLibs.spock.core
testImplementation testLibs.spock.junit4
}
if (project.hasProperty('gradle.publish.key')) {
gradlePlugin {
website = 'https://xbib.org/joerg/gradle-plugins/src/branch/main/gradle-plugin-jpackage'
vcsUrl = 'https://xbib.org/joerg/gradle-plugins'
plugins {
jpackagePlugin {
id = 'org.xbib.gradle.plugin.jpackage'
implementationClass = 'org.xbib.gradle.plugin.jpackage.JPackagePlugin'
version = project.version
description = 'Gradle JPackage plugin'
displayName = 'Gradle JPackage plugin'
tags.set(['jpackage'])
}
}
}
}

@ -0,0 +1,204 @@
package org.xbib.gradle.plugin.jpackage;
import java.util.Arrays;
import java.util.Collections;
import javax.inject.Inject;
import org.gradle.api.Action;
import org.gradle.api.NamedDomainObjectContainer;
import org.gradle.api.NonNullApi;
import org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.ConfigurationContainer;
import org.gradle.api.attributes.Attribute;
import org.gradle.api.attributes.Bundling;
import org.gradle.api.attributes.Category;
import org.gradle.api.attributes.LibraryElements;
import org.gradle.api.attributes.Usage;
import org.gradle.api.attributes.java.TargetJvmEnvironment;
import org.gradle.api.file.ConfigurableFileCollection;
import org.gradle.api.file.DirectoryProperty;
import org.gradle.api.model.ObjectFactory;
import org.gradle.api.plugins.ApplicationPlugin;
import org.gradle.api.plugins.JavaApplication;
import org.gradle.api.plugins.JavaPluginExtension;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.JavaExec;
import org.gradle.api.tasks.SourceSet;
import org.gradle.api.tasks.SourceSetContainer;
import org.gradle.api.tasks.TaskContainer;
import org.gradle.api.tasks.TaskProvider;
import org.gradle.jvm.toolchain.JavaToolchainService;
import org.gradle.nativeplatform.MachineArchitecture;
import org.gradle.nativeplatform.OperatingSystemFamily;
import static org.gradle.language.base.plugins.LifecycleBasePlugin.ASSEMBLE_TASK_NAME;
import static org.gradle.language.base.plugins.LifecycleBasePlugin.BUILD_GROUP;
import static org.gradle.nativeplatform.MachineArchitecture.ARCHITECTURE_ATTRIBUTE;
import static org.gradle.nativeplatform.OperatingSystemFamily.LINUX;
import static org.gradle.nativeplatform.OperatingSystemFamily.MACOS;
import static org.gradle.nativeplatform.OperatingSystemFamily.OPERATING_SYSTEM_ATTRIBUTE;
import static org.gradle.nativeplatform.OperatingSystemFamily.WINDOWS;
@NonNullApi
public abstract class JPackageExtension {
public abstract Property<String> getApplicationName();
public abstract Property<String> getApplicationVersion();
public abstract Property<String> getApplicationDescription();
public abstract Property<String> getVendor();
public abstract Property<String> getCopyright();
public abstract DirectoryProperty getJpackageResources();
public abstract ConfigurableFileCollection getResources();
private final NamedDomainObjectContainer<JPackageTarget> targets = getObjects().domainObjectContainer(JPackageTarget.class);
@Inject
protected abstract JavaToolchainService getJavaToolchains();
@Inject
protected abstract ObjectFactory getObjects();
@Inject
protected abstract Project getProject();
public JPackageTarget target(String label, Action<? super JPackageTarget> action) {
JPackageTarget target;
if (targets.getNames().contains(label)) {
target = targets.getByName(label);
} else {
target = targets.create(label, this::newTarget);
}
action.execute(target);
return target;
}
public JPackageTarget primaryTarget(JPackageTarget target) {
SourceSetContainer sourceSets = getProject().getExtensions().getByType(SourceSetContainer.class);
ConfigurationContainer configurations = getProject().getConfigurations();
sourceSets.all(sourceSet -> {
configureTargetAttributes(configurations.getByName(sourceSet.getCompileClasspathConfigurationName()), target);
configureTargetAttributes(configurations.getByName(sourceSet.getRuntimeClasspathConfigurationName()), target);
configurations.matching(conf -> "mainRuntimeClasspath".equals(conf.getName())).all(conf -> configureTargetAttributes(conf, target));
});
return target;
}
private void newTarget(JPackageTarget target) {
target.getPackageTypes().convention(target.getOperatingSystem().map(os -> switch (os) {
case WINDOWS -> Arrays.asList("exe", "msi");
case MACOS -> Arrays.asList("pkg", "dmg");
case LINUX -> Arrays.asList("rpm");
default -> Collections.emptyList();
}));
ConfigurationContainer configurations = getProject().getConfigurations();
SourceSetContainer sourceSets = getProject().getExtensions().getByType(SourceSetContainer.class);
sourceSets.all(sourceSet -> {
Configuration internal = maybeCreateInternalConfiguration();
configurations.create(target.getName() + capitalize(sourceSet.getCompileClasspathConfigurationName()), c -> {
c.setCanBeConsumed(false);
c.setVisible(false);
configureJavaStandardAttributes(c, Usage.JAVA_API);
configureTargetAttributes(c, target);
c.extendsFrom(
configurations.getByName(sourceSet.getImplementationConfigurationName()),
configurations.getByName(sourceSet.getCompileOnlyConfigurationName()),
internal
);
});
Configuration runtimeClasspath = configurations.create(target.getName() + capitalize(sourceSet.getRuntimeClasspathConfigurationName()), c -> {
c.setCanBeConsumed(false);
c.setVisible(false);
configureJavaStandardAttributes(c, Usage.JAVA_RUNTIME);
configureTargetAttributes(c, target);
c.extendsFrom(
configurations.getByName(sourceSet.getImplementationConfigurationName()),
configurations.getByName(sourceSet.getRuntimeOnlyConfigurationName()),
internal
);
});
if (SourceSet.isMain(sourceSet)) {
getProject().getPlugins().withType(ApplicationPlugin.class, p -> registerTargetSpecificTasks(target, sourceSet.getJarTaskName(), runtimeClasspath));
}
});
}
private void configureJavaStandardAttributes(Configuration resolvable, String usage) {
resolvable.getAttributes().attribute(Usage.USAGE_ATTRIBUTE, getObjects().named(Usage.class, usage));
resolvable.getAttributes().attribute(Category.CATEGORY_ATTRIBUTE, getObjects().named(Category.class, Category.LIBRARY));
resolvable.getAttributes().attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, getObjects().named(LibraryElements.class, LibraryElements.JAR));
resolvable.getAttributes().attribute(TargetJvmEnvironment.TARGET_JVM_ENVIRONMENT_ATTRIBUTE, getObjects().named(TargetJvmEnvironment.class, TargetJvmEnvironment.STANDARD_JVM));
resolvable.getAttributes().attribute(Bundling.BUNDLING_ATTRIBUTE, getObjects().named(Bundling.class, Bundling.EXTERNAL));
resolvable.getAttributes().attribute(Attribute.of("javaModule", Boolean.class), true);
}
private void configureTargetAttributes(Configuration resolvable, JPackageTarget target) {
resolvable.getAttributes().attributeProvider(OPERATING_SYSTEM_ATTRIBUTE, target.getOperatingSystem().map(name -> getObjects().named(OperatingSystemFamily.class, name)));
resolvable.getAttributes().attributeProvider(ARCHITECTURE_ATTRIBUTE, target.getArchitecture().map(name -> getObjects().named(MachineArchitecture.class, name)));
}
private void registerTargetSpecificTasks(JPackageTarget target, String applicationJarTask, Configuration runtimeClasspath) {
TaskContainer tasks = getProject().getTasks();
JavaPluginExtension java = getProject().getExtensions().getByType(JavaPluginExtension.class);
JavaApplication application = getProject().getExtensions().getByType(JavaApplication.class);
TaskProvider<JPackageTask> jpackage = tasks.register("jpackage" + capitalize(target.getName()), JPackageTask.class, t -> {
t.getJavaInstallation().convention(getJavaToolchains().compilerFor(java.getToolchain()).get().getMetadata());
t.getOperatingSystem().convention(target.getOperatingSystem());
t.getArchitecture().convention(target.getArchitecture());
t.getMainModule().convention(application.getMainModule());
t.getVersion().convention(getApplicationVersion());
t.getModulePath().from(tasks.named(applicationJarTask));
t.getModulePath().from(runtimeClasspath);
t.getApplicationName().convention(getApplicationName());
t.getJpackageResources().convention(getJpackageResources().dir(target.getOperatingSystem()));
t.getApplicationDescription().convention(getApplicationDescription());
t.getVendor().convention(getVendor());
t.getCopyright().convention(getCopyright());
t.getJavaOptions().convention(application.getApplicationDefaultJvmArgs());
t.getOptions().convention(target.getOptions());
t.getPackageTypes().convention(target.getPackageTypes());
t.getResources().from(getResources());
t.getDestination().convention(getProject().getLayout().getBuildDirectory().dir("packages/" + target.getName()));
t.getTempDirectory().convention(getProject().getLayout().getBuildDirectory().dir("tmp/jpackage/" + target.getName()));
});
tasks.register("run" + capitalize(target.getName()), JavaExec.class, t -> {
t.setGroup(ApplicationPlugin.APPLICATION_GROUP);
t.setDescription("Run this project as a JVM application on " + target.getName());
t.getJavaLauncher().convention(getJavaToolchains().launcherFor(java.getToolchain()));
t.getMainModule().convention(application.getMainModule());
t.getMainClass().convention(application.getMainClass());
t.setJvmArgs(application.getApplicationDefaultJvmArgs());
t.classpath(tasks.named("jar"), runtimeClasspath);
});
String targetAssembleLifecycle = "assemble" + capitalize(target.getName());
if (!tasks.getNames().contains(targetAssembleLifecycle)) {
TaskProvider<Task> lifecycleTask = tasks.register(targetAssembleLifecycle, t -> {
t.setGroup(BUILD_GROUP);
t.setDescription("Builds this project for " + target.getName());
});
tasks.named(ASSEMBLE_TASK_NAME, t -> t.dependsOn(lifecycleTask));
}
tasks.named(targetAssembleLifecycle, t -> t.dependsOn(jpackage));
}
private Configuration maybeCreateInternalConfiguration() {
Configuration internal = getProject().getConfigurations().findByName("internal");
if (internal != null) {
return internal;
}
return getProject().getConfigurations().create("internal", i -> {
i.setCanBeResolved(false);
i.setCanBeConsumed(false);
});
}
private String capitalize(String s) {
return s.substring(0, 1).toUpperCase() + s.substring(1);
}
}

@ -0,0 +1,26 @@
package org.xbib.gradle.plugin.jpackage;
import org.gradle.api.NonNullApi;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.file.SourceDirectorySet;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.tasks.SourceSetContainer;
@SuppressWarnings("unused")
@NonNullApi
public abstract class JPackagePlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
project.getPlugins().apply(JavaPlugin.class);
SourceSetContainer sourceSets = project.getExtensions().getByType(SourceSetContainer.class);
SourceDirectorySet mainResources = sourceSets.getByName("main").getResources();
JPackageExtension jPackageExtension = project.getExtensions().create("jpackage", JPackageExtension.class);
jPackageExtension.getApplicationName().convention(project.getName());
jPackageExtension.getApplicationVersion().convention(project.provider(() ->
(String) project.getVersion()));
jPackageExtension.getJpackageResources().convention(project.provider(() ->
project.getLayout().getProjectDirectory().dir(mainResources.getSrcDirs().iterator().next().getParent() + "/resourcesPackage")));
}
}

@ -0,0 +1,27 @@
package org.xbib.gradle.plugin.jpackage;
import javax.inject.Inject;
import org.gradle.api.provider.ListProperty;
import org.gradle.api.provider.Property;
public abstract class JPackageTarget {
private final String name;
public abstract Property<String> getOperatingSystem();
public abstract Property<String> getArchitecture();
public abstract ListProperty<String> getPackageTypes();
public abstract ListProperty<String> getOptions();
@Inject
public JPackageTarget(String name) {
this.name = name;
}
public String getName() {
return name;
}
}

@ -0,0 +1,236 @@
package org.xbib.gradle.plugin.jpackage;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.List;
import javax.inject.Inject;
import org.gradle.api.DefaultTask;
import org.gradle.api.file.ConfigurableFileCollection;
import org.gradle.api.file.Directory;
import org.gradle.api.file.DirectoryProperty;
import org.gradle.api.internal.file.FileOperations;
import org.gradle.api.provider.ListProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.CacheableTask;
import org.gradle.api.tasks.Classpath;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputDirectory;
import org.gradle.api.tasks.InputFiles;
import org.gradle.api.tasks.Internal;
import org.gradle.api.tasks.Nested;
import org.gradle.api.tasks.Optional;
import org.gradle.api.tasks.OutputDirectory;
import org.gradle.api.tasks.PathSensitive;
import org.gradle.api.tasks.PathSensitivity;
import org.gradle.api.tasks.TaskAction;
import org.gradle.jvm.toolchain.JavaInstallationMetadata;
import org.gradle.process.ExecOperations;
import static java.util.Objects.requireNonNull;
import static org.gradle.nativeplatform.OperatingSystemFamily.WINDOWS;
@CacheableTask
public abstract class JPackageTask extends DefaultTask {
@Nested
public abstract Property<JavaInstallationMetadata> getJavaInstallation();
@Input
public abstract Property<String> getOperatingSystem();
@Input
public abstract Property<String> getArchitecture();
@Input
public abstract Property<String> getMainModule();
@Input
public abstract Property<String> getVersion();
@Classpath
public abstract ConfigurableFileCollection getModulePath();
@Input
public abstract Property<String> getApplicationName();
@Input
@Optional
public abstract Property<String> getApplicationDescription();
@InputDirectory
@PathSensitive(PathSensitivity.RELATIVE)
public abstract DirectoryProperty getJpackageResources();
@InputFiles
@PathSensitive(PathSensitivity.RELATIVE)
public abstract ConfigurableFileCollection getResources();
@Input
@Optional
public abstract Property<String> getVendor();
@Input
@Optional
public abstract Property<String> getCopyright();
@Input
public abstract ListProperty<String> getJavaOptions();
@Input
public abstract ListProperty<String> getOptions();
@Input
public abstract ListProperty<String> getPackageTypes();
@OutputDirectory
public abstract DirectoryProperty getDestination();
@Internal
public abstract DirectoryProperty getTempDirectory();
@Inject
protected abstract FileOperations getFiles();
@Inject
protected abstract ExecOperations getExec();
@TaskAction
public void runJpackage() throws Exception {
getFiles().delete(getTempDirectory());
getFiles().delete(getDestination());
String os = getOperatingSystem().get();
String arch = getArchitecture().get();
String hostOs = System.getProperty("os.name").replace(" ", "").toLowerCase();
String hostArch = System.getProperty("os.arch");
validateHostSystem(arch, hostArch, os, hostOs);
Directory resourcesDir = getTempDirectory().get().dir("jpackage-resources");
Directory appImageParent = getTempDirectory().get().dir("app-image");
resourcesDir.getAsFile().mkdirs();
getFiles().copy(c -> {
c.from(getJpackageResources());
c.into(resourcesDir);
c.rename(f -> f.replace("icon", getApplicationName().get()));
});
String executableName = WINDOWS.equals(os) ? "jpackage.exe" : "jpackage";
String jpackage = getJavaInstallation().get()
.getInstallationPath().file("bin/" + executableName)
.getAsFile().getAbsolutePath();
getExec().exec(e -> {
e.commandLine(
jpackage,
"--type",
"app-image",
"--module",
getMainModule().get(),
"--resource-dir",
resourcesDir.getAsFile().getPath(),
"--app-version",
getVersion().get(),
"--module-path",
getModulePath().getAsPath(),
"--name",
getApplicationName().get(),
"--dest",
appImageParent.getAsFile().getPath()
);
if (getApplicationDescription().isPresent()) {
e.args("--description", getApplicationDescription().get());
}
if (getVendor().isPresent()) {
e.args("--vendor", getVendor().get());
}
if (getCopyright().isPresent()) {
e.args("--copyright", getCopyright().get());
}
for (String javaOption : getJavaOptions().get()) {
e.args("--java-options", javaOption);
}
});
File appImageFolder = requireNonNull(appImageParent.getAsFile().listFiles())[0];
File appResourcesFolder;
if (os.contains("macos")) {
appResourcesFolder = new File(appImageFolder, "Contents/app");
} else if (os.contains("windows")) {
appResourcesFolder = new File(appImageFolder, "app");
} else {
appResourcesFolder = new File(appImageFolder, "lib/app");
}
getFiles().copy(c -> {
c.from(getResources());
c.into(appResourcesFolder);
});
getPackageTypes().get().forEach(packageType ->
getExec().exec(e -> {
e.commandLine(
jpackage,
"--type",
packageType,
"--app-image",
appImageFolder.getPath(),
"--dest",
getDestination().get().getAsFile().getPath()
);
for (String option : getOptions().get()) {
e.args(option);
}
})
);
generateChecksums();
}
private void generateChecksums() throws NoSuchAlgorithmException, IOException {
File destination = getDestination().get().getAsFile();
List<File> allFiles = Arrays.stream(requireNonNull(destination.listFiles()))
.filter(File::isFile)
.toList();
for (File result : allFiles) {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] encoded = digest.digest(Files.readAllBytes(result.toPath()));
Files.write(new File(destination, result.getName() + ".sha256").toPath(), bytesToHex(encoded).getBytes());
}
}
private String bytesToHex(byte[] hash) {
StringBuilder hexString = new StringBuilder(2 * hash.length);
for (byte b : hash) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) {
hexString.append('0');
}
hexString.append(hex);
}
return hexString.toString();
}
private void validateHostSystem(String arch, String hostArch, String os, String hostOs) {
if (os.contains("macos")) {
if (!hostOs.contains(os)) {
wrongHostSystemError(hostOs, os);
}
} else if (os.contains("windows")) {
if (!hostOs.contains(os)) {
wrongHostSystemError(hostOs, os);
}
} else {
if (hostOs.contains("windows") || hostOs.contains("macos")) {
wrongHostSystemError(hostOs, os);
}
}
if (arch.contains("64") && !hostArch.contains("64")) {
wrongHostSystemError(hostArch, arch);
}
if (arch.contains("aarch") && !hostArch.contains("aarch")) {
wrongHostSystemError(hostArch, arch);
}
if (!arch.contains("aarch") && hostArch.contains("aarch")) {
wrongHostSystemError(hostArch, arch);
}
}
private void wrongHostSystemError(String hostOs, String os) {
throw new RuntimeException("Running on " + hostOs + "; cannot build for " + os);
}
}

@ -0,0 +1,58 @@
package org.xbib.gradle.plugin.jpackage
import org.xbib.gradle.plugin.jpackage.fixture.GradleBuild
import spock.lang.Specification
import static org.gradle.testkit.runner.TaskOutcome.FAILED
import static org.gradle.testkit.runner.TaskOutcome.SUCCESS
import static org.xbib.gradle.plugin.jpackage.fixture.GradleBuild.hostOs
import static org.xbib.gradle.plugin.jpackage.fixture.GradleBuild.runsOnLinux
import static org.xbib.gradle.plugin.jpackage.fixture.GradleBuild.runsOnMacos
import static org.xbib.gradle.plugin.jpackage.fixture.GradleBuild.runsOnWindows
class JPackageTest extends Specification {
@Delegate
GradleBuild build = new GradleBuild()
def "can use plugin on #os with success=#success"() {
given:
def taskToRun = ":app:assemble${label.capitalize()}"
def taskToCheck = ":app:jpackage${label.capitalize()}"
def macosArch = System.getProperty('os.arch').contains('aarch') ? 'aarch64' : 'x86-64'
appBuildFile << """
version = "1.0"
jpackage {
target("fedora") {
operatingSystem = "linux"
architecture = "x86-64"
}
target("macos") {
operatingSystem = "macos"
architecture = "$macosArch"
}
target("windows") {
operatingSystem = "windows"
architecture = "x86-64"
}
}
"""
appModuleInfoFile << '''
module org.example.app {
}
'''
when:
def result = success ? build(taskToRun) : fail(taskToRun)
then:
result.task(taskToCheck).outcome == (success ? SUCCESS : FAILED)
success || result.output.contains("> Running on ${hostOs()}; cannot build for $os")
where:
label | os | success
'fedora' | 'linux' | runsOnLinux()
'windows' | 'windows' | runsOnWindows()
'macos' | 'macos' | runsOnMacos()
}
}

@ -0,0 +1,121 @@
package org.xbib.gradle.plugin.jpackage.fixture
import org.gradle.testkit.runner.BuildResult
import org.gradle.testkit.runner.GradleRunner
import java.lang.management.ManagementFactory
import java.nio.file.Files
class GradleBuild {
final File projectDir
final File settingsFile
final File appBuildFile
final File appModuleInfoFile
final File libBuildFile
final File libModuleInfoFile
final String gradleVersionUnderTest = System.getProperty('gradleVersionUnderTest')
GradleBuild(File projectDir = Files.createTempDirectory('gradle-build').toFile()) {
this.projectDir = projectDir
this.settingsFile = file('settings.gradle.kts')
this.appBuildFile = file('app/build.gradle.kts')
this.appModuleInfoFile = file('app/src/main/java/module-info.java')
this.libBuildFile = file('lib/build.gradle.kts')
this.libModuleInfoFile = file('lib/src/main/java/module-info.java')
file('app/src/main/resourcesPackage/windows').mkdirs()
file('app/src/main/resourcesPackage/macos').mkdirs()
file('app/src/main/resourcesPackage/linux').mkdirs()
settingsFile << '''
dependencyResolutionManagement { repositories.mavenCentral() }
includeBuild(".")
rootProject.name = "test-project"
include("app", "lib")
'''
appBuildFile << '''
plugins {
id("org.xbib.gradle.plugin.jpackage")
id("application")
}
group = "org.example"
application {
mainModule.set("org.example.app")
mainClass.set("org.example.app.Main")
}
'''
file("app/src/main/java/org/example/app/Main.java") << '''
package org.example.app;
public class Main {
public static void main(String... args) {
}
}
'''
file("app/src/test/java/org/example/app/test/MainTest.java") << '''
package org.example.app.test;
import org.junit.jupiter.api.Test;
import org.example.app.Main;
public class MainTest {
@Test
void testApp() {
new Main();
}
}
'''
libBuildFile << '''
plugins {
id("org.xbib.gradle.plugin.jpackage")
id("java-library")
}
group = "org.example"
'''
}
File file(String path) {
new File(projectDir, path).tap {
it.getParentFile().mkdirs()
}
}
static boolean runsOnWindows() {
hostOs().contains('win')
}
static boolean runsOnMacos() {
hostOs().contains('mac')
}
static boolean runsOnLinux() {
!runsOnWindows() && !runsOnMacos()
}
static String hostOs() {
System.getProperty("os.name").replace(" ", "").toLowerCase()
}
BuildResult build(taskToRun) {
runner(taskToRun).build()
}
BuildResult fail(taskToRun) {
runner(taskToRun).buildAndFail()
}
GradleRunner runner(String... args) {
GradleRunner.create()
.forwardOutput()
.withPluginClasspath()
.withProjectDir(projectDir)
.withArguments(Arrays.asList(args) + '-s')
.withDebug(ManagementFactory.getRuntimeMXBean().getInputArguments().toString().contains("-agentlib:jdwp")).with {
gradleVersionUnderTest ? it.withGradleVersion(gradleVersionUnderTest) : it
}
}
}

@ -7,6 +7,7 @@ apply plugin: 'java-gradle-plugin'
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()
@ -14,31 +15,28 @@ dependencies {
testImplementation gradleTestKit()
}
gradlePlugin {
plugins {
rpmPlugin {
id = 'org.xbib.gradle.plugin.rpm'
implementationClass = 'org.xbib.gradle.plugin.RpmPlugin'
publishing {
repositories {
maven {
name = 'localRepository'
url = 'build/local-repository'
}
}
}
if (project.hasProperty('gradle.publish.key')) {
pluginBundle {
mavenCoordinates {
groupId = "org.xbib.gradle.plugin"
artifactId = "gradle-plugin-rpm"
version = project.version
}
website = 'https://github.com/jprante/gradle-plugins'
vcsUrl = 'https://github.com/jprante/gradle-plugins'
gradlePlugin {
website = 'https://xbib.org/joerg/gradle-plugins/src/branch/main/gradle-plugin-rpm'
vcsUrl = 'https://xbib.org/joerg/gradle-plugins'
plugins {
rpmPlugin {
id = 'org.xbib.gradle.plugin.rpm'
group = project.group
version = project.version
description = 'Java implementation for RPM packaging'
displayName = 'Java implementation for RPM packaging'
tags = [ 'rpm' ]
implementationClass = 'org.xbib.gradle.plugin.RpmPlugin'
tags.set(['rpm'])
}
}
}

@ -1 +1,2 @@
version = 2.3.0
name = gradle-plugin-rpm
version = 3.1.0

@ -11,6 +11,8 @@ import org.xbib.rpm.Link
import org.xbib.rpm.lead.Architecture
import org.xbib.rpm.lead.Os
import org.xbib.rpm.lead.PackageType
import org.xbib.rpm.payload.CompressionType
import org.xbib.rpm.security.HashAlgo
import java.util.concurrent.Callable
@ -125,6 +127,14 @@ class Rpm extends AbstractArchiveTask {
@Input
Integer gid
@Optional
@Input
Boolean setuid
@Optional
@Input
Boolean setgid
@Optional
@Input
String maintainer
@ -269,9 +279,24 @@ class Rpm extends AbstractArchiveTask {
@Input
List<Dependency> provides
@Optional
@Input
HashAlgo privateKeyHashAlgo
@Optional
@Input
HashAlgo fileDigestsHashAlgo
@Optional
@Input
CompressionType compressionType
Rpm() {
super()
// sane defaults
privateKeyHashAlgo = HashAlgo.SHA256
fileDigestsHashAlgo = HashAlgo.MD5
compressionType = CompressionType.GZIP
os = Os.LINUX
arch = Architecture.AMD64
packageType = PackageType.BINARY
@ -332,10 +357,15 @@ class Rpm extends AbstractArchiveTask {
sb.toString()
}
Directory directory(String path) {
Directory directory = new Directory(path: path)
directories << directory
directories.add(directory)
directory
}
Directory directory(String path, String user, String group) {
Directory directory = new Directory(path: path, user: user, group: group)
directories.add(directory)
directory
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save