diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle index de14e160..f8ab27fe 100644 --- a/buildSrc/build.gradle +++ b/buildSrc/build.gradle @@ -5,10 +5,12 @@ plugins { repositories { jcenter() gradlePluginPortal() + maven { url = 'https://repo.nokee.dev/snapshot' } } dependencies { implementation gradleApi() + implementation platform('dev.nokee:nokee-gradle-plugins:0.4.1229-202112011142.4a01703c') implementation 'org.apache.httpcomponents.client5:httpclient5:5.0.1' implementation 'com.google.guava:guava:28.2-jre' implementation 'org.gradle:test-retry-gradle-plugin:1.1.8' @@ -30,6 +32,10 @@ gradlePlugin { id = "gradlebuild.jni" implementationClass = "gradlebuild.JniPlugin" } + jniNokee { + id = "gradlebuild.jni-nokee" + implementationClass = "gradlebuild.JniNokeePlugin" + } nativeComponent { id = "gradlebuild.native-platform-component" implementationClass = "gradlebuild.NativePlatformComponentPlugin" diff --git a/buildSrc/src/main/java/gradlebuild/JniNokeePlugin.java b/buildSrc/src/main/java/gradlebuild/JniNokeePlugin.java new file mode 100644 index 00000000..6883c997 --- /dev/null +++ b/buildSrc/src/main/java/gradlebuild/JniNokeePlugin.java @@ -0,0 +1,213 @@ +package gradlebuild; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import dev.nokee.language.cpp.CppSourceSet; +import dev.nokee.platform.jni.JavaNativeInterfaceLibrary; +import dev.nokee.runtime.nativebase.TargetMachine; +import dev.nokee.runtime.nativebase.TargetMachineFactory; +import groovy.util.Node; +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.plugins.ExtensionAware; +import org.gradle.api.plugins.JavaPluginConvention; +import org.gradle.api.provider.SetProperty; +import org.gradle.api.publish.PublishingExtension; +import org.gradle.api.publish.maven.MavenPublication; +import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.TaskContainer; +import org.gradle.api.tasks.TaskProvider; +import org.gradle.api.tasks.compile.JavaCompile; +import org.gradle.language.cpp.tasks.CppCompile; +import org.gradle.model.Mutate; +import org.gradle.model.RuleSource; +import org.gradle.nativeplatform.platform.OperatingSystem; +import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform; +import org.gradle.nativeplatform.tasks.LinkSharedLibrary; +import org.gradle.nativeplatform.toolchain.Clang; +import org.gradle.nativeplatform.toolchain.Gcc; +import org.gradle.nativeplatform.toolchain.NativeToolChainRegistry; +import org.gradle.nativeplatform.toolchain.VisualCpp; + +import java.io.File; +import java.util.Set; + +@SuppressWarnings("UnstableApiUsage") +public abstract class JniNokeePlugin implements Plugin { + + @Override + public void apply(Project project) { + project.getPluginManager().apply(NativePlatformComponentPlugin.class); + VariantsExtension variants = project.getExtensions().getByType(VariantsExtension.class); + project.getPluginManager().apply("dev.nokee.jni-library"); + project.getPluginManager().apply(NativeToolChainRules.class); + + configureCppTasks(project); + configureMainLibrary(project); + configureVariants(project); + addComponentSourcesSetsToProjectSourceSet(project.getTasks(), project.getExtensions().getByType(JavaNativeInterfaceLibrary.class)); + + configureNativeVersionGeneration(project); + + configurePomOfMainJar(project, variants); + } + + private void configureVariants(Project project) { + JavaNativeInterfaceLibrary library = project.getExtensions().getByType(JavaNativeInterfaceLibrary.class); + VariantsExtension variants = project.getExtensions().getByType(VariantsExtension.class); + variants.getVariantNames().set(library.getVariants().flatMap(it -> { + // Only depend on variants which can be built on the current machine + boolean onlyLocalVariants = project.getProviders().gradleProperty("onlyLocalVariants").forUseAtConfigurationTime().isPresent(); + if (onlyLocalVariants && !it.getSharedLibrary().isBuildable()) { + return ImmutableList.of(); + } else { + return ImmutableList.of(toVariantName(it.getTargetMachine())); + } + })); + } + + private static String toVariantName(TargetMachine targetMachine) { + return targetMachine.getOperatingSystemFamily().getName() + "-" + targetMachine.getArchitecture().getName(); + } + + private void configureMainLibrary(Project project) { + JavaNativeInterfaceLibrary library = project.getExtensions().getByType(JavaNativeInterfaceLibrary.class); + registerWindowsDistributionDimension(library); + library.getTargetMachines().set(supportedMachines(library.getMachines())); + library.getTasks().configureEach(CppCompile.class, task -> { + task.getCompilerArgs().addAll(task.getTargetPlatform().map(targetPlatform -> { + OperatingSystem targetOs = targetPlatform.getOperatingSystem(); + if (targetOs.isMacOsX()) { + return ImmutableList.of("-mmacosx-version-min=10.9"); + } else if (targetOs.isLinux()) { + return ImmutableList.of("-D_FILE_OFFSET_BITS=64"); + } else { + return ImmutableList.of(); // do nothing + } + })); + }); + library.getTasks().configureEach(LinkSharedLibrary.class, task -> { + task.getLinkerArgs().addAll(task.getTargetPlatform().map(targetPlatform -> { + OperatingSystem targetOs = targetPlatform.getOperatingSystem(); + if (targetOs.isMacOsX()) { + return ImmutableList.of( + "-mmacosx-version-min=10.9", + "-framework", "CoreServices"); + } else if (targetOs.isWindows()) { + return ImmutableList.of("Shlwapi.lib", "Advapi32.lib"); + } else { + return ImmutableList.of(); // do nothing + } + })); + }); + library.getVariants().configureEach(variant -> { + if (variant.getBuildVariant().hasAxisOf(WindowsDistribution.WINDOWS_XP_OR_LOWER)) { + variant.getTasks().configureEach(CppCompile.class, task -> { + task.getCompilerArgs().add("/DWINDOWS_MIN"); + }); + } + }); + library.getVariants().configureEach(variant -> { + variant.getResourcePath().set(String.join("/", + project.getGroup().toString().replace('.', '/'), + "platform", + toVariantName(variant.getTargetMachine()))); + }); + } + + private void registerWindowsDistributionDimension(JavaNativeInterfaceLibrary library) { + SetProperty newDimension = library.getDimensions().newAxis(WindowsDistribution.class, builder -> builder.onlyOn(library.getMachines().getWindows().getOperatingSystemFamily())); + newDimension.convention(ImmutableSet.copyOf(WindowsDistribution.values())); + ((ExtensionAware) library).getExtensions().add("targetWindowsDistributions", newDimension); + } + + private void addComponentSourcesSetsToProjectSourceSet(TaskContainer tasks, JavaNativeInterfaceLibrary library) { + tasks.withType(WriteNativeVersionSources.class, task -> { + task.getNativeSources().from(library.getSources().flatMap(sourceSet -> { + if (sourceSet instanceof CppSourceSet) { + return ImmutableList.of(sourceSet.getSourceDirectories(), ((CppSourceSet) sourceSet).getHeaders().getSourceDirectories()); + } else { + return ImmutableList.of(); + } + })); + }); + } + + private void configureCppTasks(Project project) { + TaskContainer tasks = project.getTasks(); + TaskProvider compileJavaProvider = tasks.named("compileJava", JavaCompile.class); + tasks.withType(CppCompile.class) + .configureEach(task -> task.includes( + compileJavaProvider.flatMap(it -> it.getOptions().getHeaderOutputDirectory()) + )); + } + + private static Set supportedMachines(TargetMachineFactory machines) { + ImmutableSet.Builder result = ImmutableSet.builder(); + result.add(machines.os("osx").architecture("amd64")); + result.add(machines.os("osx").architecture("aarch64")); + result.add(machines.getLinux().architecture("amd64")); + result.add(machines.getLinux().architecture("aarch64")); + result.add(machines.getWindows().architecture("i386")); + result.add(machines.getWindows().architecture("amd64")); + return result.build(); + } + + private void configureNativeVersionGeneration(Project project) { + NativePlatformVersionExtension nativeVersion = project.getExtensions().create("nativeVersion", NativePlatformVersionExtension.class); + + File generatedFilesDir = new File(project.getBuildDir(), "generated"); + + TaskProvider writeNativeVersionSources = project.getTasks().register("writeNativeVersionSources", WriteNativeVersionSources.class, task -> { + task.getGeneratedNativeHeaderDirectory().set(new File(generatedFilesDir, "version/header")); + task.getGeneratedJavaSourcesDir().set(new File(generatedFilesDir, "version/java")); + task.getVersionClassPackageName().set(nativeVersion.getVersionClassPackageName()); + task.getVersionClassName().set(nativeVersion.getVersionClassName()); + }); + + + project.getTasks().withType(CppCompile.class).configureEach(task -> + task.includes(writeNativeVersionSources.flatMap(WriteNativeVersionSources::getGeneratedNativeHeaderDirectory) + )); + JavaPluginConvention javaPluginConvention = project.getConvention().getPlugin(JavaPluginConvention.class); + javaPluginConvention.getSourceSets().getByName(SourceSet.MAIN_SOURCE_SET_NAME).java(javaSources -> + javaSources.srcDir(writeNativeVersionSources.flatMap(WriteNativeVersionSources::getGeneratedJavaSourcesDir)) + ); + } + + private void configurePomOfMainJar(Project project, VariantsExtension variants) { + project.getExtensions().configure( + PublishingExtension.class, + extension -> extension.getPublications().named("main", MavenPublication.class, main -> + main.getPom().withXml(xmlProvider -> { + Node node = xmlProvider.asNode(); + Node deps = node.appendNode("dependencies"); + variants.getVariantNames().get().forEach(variantName -> { + Node dep = deps.appendNode("dependency"); + dep.appendNode("groupId", project.getGroup()); + dep.appendNode("artifactId", main.getArtifactId() + "-" + variantName); + dep.appendNode("version", project.getVersion()); + dep.appendNode("scope", "runtime"); + }); + }))); + } + + public static class NativeToolChainRules extends RuleSource { + @Mutate + void createToolChains(NativeToolChainRegistry toolChainRegistry) { + toolChainRegistry.create("gcc", Gcc.class, toolChain -> { + // The core Gradle toolchain for gcc only targets x86 and x86_64 out of the box. + // https://github.com/gradle/gradle/blob/36614ee523e5906ddfa1fed9a5dc00a5addac1b0/subprojects/platform-native/src/main/java/org/gradle/nativeplatform/toolchain/internal/gcc/AbstractGccCompatibleToolChain.java + toolChain.target("linuxaarch64"); + }); + toolChainRegistry.create("clang", Clang.class, toolChain -> { + // The core Gradle toolchain for Clang only targets x86 and x86_64 out of the box. + OperatingSystem os = new DefaultNativePlatform("current").getOperatingSystem(); + if (os.isMacOsX()) { + toolChain.target("macosaarch64"); + } + }); + toolChainRegistry.create("visualCpp", VisualCpp.class); + } + } +} diff --git a/buildSrc/src/main/java/gradlebuild/NcursesVersion.java b/buildSrc/src/main/java/gradlebuild/NcursesVersion.java new file mode 100644 index 00000000..0e6f066c --- /dev/null +++ b/buildSrc/src/main/java/gradlebuild/NcursesVersion.java @@ -0,0 +1,18 @@ +package gradlebuild; + +import org.gradle.api.Named; + +public enum NcursesVersion implements Named { + NCURSES_5("5"), NCURSES_6("6"); + + private final String versionNumber; + + NcursesVersion(String versionNumber) { + this.versionNumber = versionNumber; + } + + @Override + public String getName() { + return "ncurses" + versionNumber; + } +} diff --git a/buildSrc/src/main/java/gradlebuild/WindowsDistribution.java b/buildSrc/src/main/java/gradlebuild/WindowsDistribution.java new file mode 100644 index 00000000..4d8c414e --- /dev/null +++ b/buildSrc/src/main/java/gradlebuild/WindowsDistribution.java @@ -0,0 +1,18 @@ +package gradlebuild; + +import org.gradle.api.Named; + +public enum WindowsDistribution implements Named { + WINDOWS_XP_OR_LOWER { + @Override + public String getName() { + return "min"; + } + }, + WINDOWS_VISTA_OR_HIGHER { + @Override + public String getName() { + return ""; + } + }; +} diff --git a/file-events/build.gradle b/file-events/build.gradle index 89758aee..831c5fad 100755 --- a/file-events/build.gradle +++ b/file-events/build.gradle @@ -1,9 +1,11 @@ plugins { id 'groovy' - id 'cpp' - id 'gradlebuild.jni' + id 'dev.nokee.cpp-language' + id 'gradlebuild.jni-nokee' } +import dev.nokee.language.cpp.CppSourceSet + nativeVersion { versionClassPackageName = "net.rubygrapefruit.platform.internal.jni" versionClassName = "FileEventsVersion" @@ -19,49 +21,58 @@ javadoc { exclude '**/internal/**' } -model { - components { - nativePlatformFileEvents(NativeLibrarySpec) { - baseName 'native-platform-file-events' - $.platforms.each { p -> - targetPlatform p.name - } - binaries.all { - if (targetPlatform.operatingSystem.macOsX - || targetPlatform.operatingSystem.linux) { - cppCompiler.args "-g" // Produce debug output - cppCompiler.args "-pthread" // Force nicer threading - cppCompiler.args "-pedantic" // Disable non-standard things - cppCompiler.args "--std=c++11" // Enable C++11 - cppCompiler.args "-Wall" // All warnings - cppCompiler.args "-Wextra" // Plus extra - cppCompiler.args "-Wformat=2" // Check printf format strings - cppCompiler.args "-Werror" // Warnings are errors - cppCompiler.args "-Wno-format-nonliteral" // Allow printf to have dynamic format string - cppCompiler.args "-Wno-unguarded-availability-new" // Newly introduced flags are not available on older macOS versions - linker.args "-pthread" - } else if (targetPlatform.operatingSystem.windows) { - cppCompiler.args "/DEBUG" // Produce debug output - cppCompiler.args "/std:c++17" // Won't hurt - cppCompiler.args "/permissive-" // Make compiler more standards compatible - cppCompiler.args "/EHsc" // Force exception handling mode - cppCompiler.args "/Zi" // Force PDB debugging - cppCompiler.args "/FS" // Force synchronous PDB writes - cppCompiler.args "/Zc:inline" // Hack - cppCompiler.args "/Zc:throwingNew" // Assume new throws on error - cppCompiler.args "/W3" // Enable lots of warnings, disbale individual warnings with /WD - cppCompiler.args "/WX" // Warnings are errors - cppCompiler.args "/D_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING" - // Don't issue warnings for wstring_convert in generic_fsnotifier.cpp - linker.args "/DEBUG:FULL" // Generate all PDBs - } +library { + baseName = 'native-platform-file-events' + sources.configureEach(CppSourceSet) { + source.from('src/file-events/cpp') + headers.from('src/file-events/headers') + } + tasks.configureEach(CppCompile) { + compilerArgs.addAll(targetPlatform.map { + if (it.operatingSystem.macOsX + || it.operatingSystem.linux) { + [ + "-g", // Produce debug output + "-pthread", // Force nicer threading + "-pedantic", // Disable non-standard things + "--std=c++11", // Enable C++11 + "-Wall", // All warnings + "-Wextra", // Plus extra + "-Wformat=2", // Check printf format strings + "-Werror", // Warnings are errors + "-Wno-format-nonliteral", // Allow printf to have dynamic format string + "-Wno-unguarded-availability-new", // Newly introduced flags are not available on older macOS versions + ] + } else if (it.operatingSystem.windows) { + [ + "/DEBUG", // Produce debug output + "/std:c++17", // Won't hurt + "/permissive-", // Make compiler more standards compatible + "/EHsc", // Force exception handling mode + "/Zi", // Force PDB debugging + "/FS", // Force synchronous PDB writes + "/Zc:inline", // Hack + "/Zc:throwingNew", // Assume new throws on error + "/W3", // Enable lots of warnings, disbale individual warnings with /WD + "/WX", // Warnings are errors + "/D_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING", + // Don't issue warnings for wstring_convert in generic_fsnotifier.cpp + ] + } else { + [] } - sources { - cpp { - source.srcDirs = ['src/file-events/cpp'] - exportedHeaders.srcDirs = ['src/file-events/headers'] - } + }) + } + tasks.configureEach(LinkSharedLibrary) { + linkerArgs.addAll(targetPlatform.map { + if (it.operatingSystem.macOsX + || it.operatingSystem.linux) { + ["-pthread"] + } else if (it.operatingSystem.windows) { + ["/DEBUG:FULL"] // Generate all PDBs + } else { + [] } - } + }) } } diff --git a/native-platform/build.gradle b/native-platform/build.gradle index 24183a76..76aa2512 100755 --- a/native-platform/build.gradle +++ b/native-platform/build.gradle @@ -1,12 +1,15 @@ plugins { id 'groovy' id 'java-test-fixtures' - id 'cpp' - id 'gradlebuild.jni' - id 'gradlebuild.freebsd' - id 'gradlebuild.ncurses' + id 'dev.nokee.cpp-language' + id 'gradlebuild.jni-nokee' +// id 'gradlebuild.freebsd' +// id 'gradlebuild.ncurses' } +import dev.nokee.language.cpp.CppSourceSet +import gradlebuild.NcursesVersion + nativeVersion { versionClassPackageName = "net.rubygrapefruit.platform.internal.jni" versionClassName = "NativeVersion" @@ -20,41 +23,15 @@ javadoc { exclude '**/internal/**' } -model { - components { - nativePlatform(NativeLibrarySpec) { - baseName 'native-platform' - $.platforms.each { p -> - if (p.name.contains("ncurses")) { - return - } - targetPlatform p.name - } - sources { - cpp { - source.srcDirs = ['src/shared/cpp', 'src/main/cpp'] - exportedHeaders.srcDirs = ['src/shared/headers'] - } - } - } - - nativePlatformCurses(NativeLibrarySpec) { - baseName 'native-platform-curses' - $.platforms.each { p -> - if (p.operatingSystem.windows) { - return - } - if (p.operatingSystem.linux && !p.name.contains("ncurses")) { - return - } - targetPlatform p.name - } - sources { - cpp { - source.srcDirs = ['src/shared/cpp', 'src/curses/cpp'] - exportedHeaders.srcDirs = ['src/shared/headers'] - } - } +library { + baseName = 'native-platform' + sources.configureEach(CppSourceSet) { + source.from('src/shared/cpp', 'src/main/cpp') + headers.from('src/shared/headers') + } + variants.configureEach { + if (NcursesVersion.values().any { buildVariant.hasAxisOf(it) }) { + baseName = 'native-platform-curses' } } }