diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..8acf4c8 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,110 @@ +name: CI + +on: [push, pull_request] + +jobs: + build-docker: + strategy: + fail-fast: false + matrix: + include: + - container: wpilib/roborio-cross-ubuntu:2024-22.04 + artifact-name: Athena + build-options: "-Ponlylinuxathena" + # - container: wpilib/raspbian-cross-ubuntu:bullseye-22.04 + # artifact-name: Arm32 + # build-options: "-Ponlylinuxarm32" + # - container: wpilib/aarch64-cross-ubuntu:bullseye-22.04 + # artifact-name: Arm64 + # build-options: "-Ponlylinuxarm64" + - container: wpilib/ubuntu-base:22.04 + artifact-name: Linux + build-options: "" + name: "Build - ${{ matrix.artifact-name }}" + runs-on: ubuntu-latest + container: ${{ matrix.container }} + steps: + - uses: actions/checkout@v4 + with: + submodules: true + fetch-depth: 0 + - name: Fetch all history and metadata + run: | + git config --global --add safe.directory /__w/${{ github.event.repository.name }}/${{ github.event.repository.name }} + - name: Build with Gradle + run: ./gradlew build ${{ matrix.build-options }} + - uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.artifact-name }} + path: build/allOutputs + + build-host: + env: + MACOSX_DEPLOYMENT_TARGET: 13 + strategy: + fail-fast: false + matrix: + include: + - os: windows-2022 + artifact-name: Win64 + architecture: x64 + - os: macos-14 + artifact-name: macOS + architecture: x64 + name: "Build - ${{ matrix.artifact-name }}" + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - name: Fetch all history and metadata + run: git fetch --prune --unshallow + - uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: 17 + architecture: ${{ matrix.architecture }} + - name: Build with Gradle + run: ./gradlew build -Pbuildalldesktop + - uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.artifact-name }} + path: build/allOutputs + + combine: + name: Combine + needs: [build-docker, build-host] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + repository: wpilibsuite/build-tools + - uses: actions/download-artifact@v4 + with: + path: combiner/products/build/allOutputs + - name: Flatten Artifacts + run: rsync -a --delete combiner/products/build/allOutputs/*/* combiner/products/build/allOutputs/ + - uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: 17 + - name: Combine + # if: | + # !startsWith(github.ref, 'refs/tags/v') + run: ./gradlew publish -Pthirdparty + working-directory: combiner + # - name: Combine (Release) + # if: | + # github.repository_owner == 'wpilibsuite' && + # startsWith(github.ref, 'refs/tags/v') + # run: | + # ./gradlew publish -Pthirdparty + # working-directory: combiner + # env: + # RUN_AZURE_ARTIFACTORY_RELEASE: 'TRUE' + # ARTIFACTORY_PUBLISH_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }} + # ARTIFACTORY_PUBLISH_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }} + - uses: actions/upload-artifact@v4 + with: + name: Maven + path: ~/releases diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a332fb2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,160 @@ +# Created by https://www.gitignore.io/api/c++,java,linux,macos,gradle,windows,visualstudiocode + +### C++ ### +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +### Java ### +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +### Gradle ### +.gradle +/build/ + +# Ignore Gradle GUI config +gradle-app.setting + +# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) +!gradle-wrapper.jar + +# Cache of project +.gradletasknamecache + +# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 +# gradle/wrapper/gradle-wrapper.properties + +# # VS Code Specific Java Settings +.classpath +.project +.settings/ +bin/ + + +# End of https://www.gitignore.io/api/c++,java,linux,macos,gradle,windows,visualstudiocode diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..cfccbf4 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,10 @@ +{ + "java.configuration.updateBuildConfiguration": "automatic", + "files.exclude": { + "**/.classpath": true, + "**/.project": true, + "**/.settings": true, + "**/.factorypath": true + }, + "C_Cpp.default.configurationProvider": "vscode-wpilib" +} \ No newline at end of file diff --git a/.wpilib/wpilib_preferences.json b/.wpilib/wpilib_preferences.json new file mode 100644 index 0000000..801c0d0 --- /dev/null +++ b/.wpilib/wpilib_preferences.json @@ -0,0 +1,6 @@ +{ + "enableCppIntellisense": true, + "currentLanguage": "cpp", + "projectYear": "intellisense", + "teamNumber": 9999 +} diff --git a/ExampleVendorJson.json b/ExampleVendorJson.json new file mode 100644 index 0000000..e5188c9 --- /dev/null +++ b/ExampleVendorJson.json @@ -0,0 +1,74 @@ +{ + "fileName": "ExampleVendorJson.json", + "name": "ExampleVendorDep", + "version": "0.0.1", + "frcYear": "2024", + "uuid": "Generate A Unique GUID https://guidgenerator.com/online-guid-generator.aspx and insert it here", This line is to purposely make this fail to parse + "mavenUrls": [ + "ThisNeedsToBeTheRootMavenUrl" + ], + "jsonUrl": "InsertSomeUrlHere", + "javaDependencies": [ + { + "groupId": "com.vendor.frc", + "artifactId": "Vendor-java", + "version": "0.0.1" + } + ], + "jniDependencies": [ + { + "groupId": "com.vendor.frc", + "artifactId": "Vendor-driver", + "version": "0.0.1", + "skipInvalidPlatforms": true, + "isJar": false, + "validPlatforms": [ + "windowsx86-64", + "windowsx86", + "linuxarm64", + "linuxx86-64", + "linuxathena", + "linuxarm32", + "osxuniversal" + ] + } + ], + "cppDependencies": [ + { + "groupId": "com.vendor.frc", + "artifactId": "Vendor-cpp", + "version": "0.0.1", + "libName": "Vendor", + "headerClassifier": "headers", + "sharedLibrary": false, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "windowsx86", + "linuxarm64", + "linuxx86-64", + "linuxathena", + "linuxarm32", + "osxuniversal" + ] + }, + { + "groupId": "com.vendor.frc", + "artifactId": "Vendor-driver", + "version": "0.0.1", + "libName": "VendorDriver", + "headerClassifier": "headers", + "sharedLibrary": false, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "windowsx86", + "linuxarm64", + "linuxx86-64", + "linuxathena", + "linuxarm32", + "osxuniversal" + ] + } + ] +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..0ebfba8 --- /dev/null +++ b/README.md @@ -0,0 +1,25 @@ +# WPILib Vendor Template + +This is the base WPILib vendor template for 2023. + +## Layout + +The build is split into 3 libraries. A java library is built. This has access to all of wpilib, and also can JNI load the driver library. + +A driver library is built. This should contain all low level code you want to access from both C++, Java and any other text based language. This will not work with LabVIEW. This library has access to the WPILib HAL and wpiutil. This library can only export C symbols. It cannot export C++ symbols at all, and all C symbols must be explicitly listed in the symbols.txt file in the driver folder. JNI symbols must be listed in this file as well. This library however can be written in C++. If you attempt to change this library to have access to all of wpilib, you will break JNI access and it will no longer work. + +A native C++ library is built. This has access to all of wpilib, and access to the driver library. This should implment the standard wpilib interfaces. + +## Customizing +For Java, the library name will be the folder name the build is started from, so rename the folder to the name of your choosing. + +For the native impl, you need to change the library name in the exportsConfigs block of build.gradle, the components block of build.gradle, and the taskList input array name in publish.gradle. + +For the driver, change the library name in privateExportsConfigs, the driver name in components, and the driverTaskList input array name. In addition, you'll need to change the `lib library` in the native C++ impl component, and the JNI library name in the JNI java class. + +For the maven artifact names, those are all in publish.gradle about 40 lines down. + +## Building and editing +This uses gradle, and uses the same base setup as a standard GradleRIO robot project. This means you build with `./gradlew build`, and can install the native toolchain with `./gradlew installRoboRIOToolchain`. If you open this project in VS Code with the wpilib extension installed, you will get intellisense set up for both C++ and Java. + +By default, this template builds against the latest WPILib development build. To build against the last WPILib tagged release, build with `./gradlew build -PreleaseMode`. \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..bb4238b --- /dev/null +++ b/build.gradle @@ -0,0 +1,130 @@ +plugins { + id 'cpp' + id 'java' + id 'google-test' + id 'edu.wpi.first.wpilib.repositories.WPILibRepositoriesPlugin' version '2020.2' + id 'edu.wpi.first.NativeUtils' version '2024.7.2' + id 'edu.wpi.first.GradleJni' version '1.1.0' + id 'edu.wpi.first.GradleVsCode' version '2.1.0' +} + +repositories { + mavenCentral() +} +if (project.hasProperty('releaseMode')) { + wpilibRepositories.addAllReleaseRepositories(project) +} else { + wpilibRepositories.addAllDevelopmentRepositories(project) +} + +// Apply C++ configuration +apply from: 'config.gradle' + +// Apply Java configuration +dependencies { + implementation 'edu.wpi.first.cscore:cscore-java:2024.+' + implementation 'edu.wpi.first.cameraserver:cameraserver-java:2024.+' + implementation 'edu.wpi.first.ntcore:ntcore-java:2024.+' + implementation 'edu.wpi.first.wpilibj:wpilibj-java:2024.+' + implementation 'edu.wpi.first.wpiutil:wpiutil-java:2024.+' + implementation 'edu.wpi.first.wpimath:wpimath-java:2024.+' + implementation 'edu.wpi.first.wpiunits:wpiunits-java:2024.+' + implementation 'edu.wpi.first.hal:hal-java:2024.+' + implementation "org.ejml:ejml-simple:0.43.1" + implementation "com.fasterxml.jackson.core:jackson-annotations:2.15.2" + implementation "com.fasterxml.jackson.core:jackson-core:2.15.2" + implementation "com.fasterxml.jackson.core:jackson-databind:2.15.2" + implementation 'edu.wpi.first.thirdparty.frc2024.opencv:opencv-java:4.8.0-2' +} + +// Set up exports properly +nativeUtils { + exportsConfigs { + // Main library is just default empty. This will export everything + Vendor { + } + } + privateExportsConfigs { + // Only export explicit symbols from driver library + VendorDriver { + exportsFile = project.file("src/main/driver/symbols.txt") + } + } +} + +model { + components { + Vendor(NativeLibrarySpec) { + sources { + cpp { + source { + srcDirs 'src/main/native/cpp' + include '**/*.cpp' + } + exportedHeaders { + srcDirs 'src/main/native/include' + } + } + } + binaries.all { + lib library: 'VendorDriver', linkage: 'shared' + } + nativeUtils.useRequiredLibrary(it, 'wpilib_shared') + } + + VendorDriver(JniNativeLibrarySpec) { + enableCheckTask true + javaCompileTasks << compileJava + jniCrossCompileOptions << JniCrossCompileOptions(nativeUtils.wpi.platforms.roborio) + // Leave these for future proofing + jniCrossCompileOptions << JniCrossCompileOptions(nativeUtils.wpi.platforms.linuxarm32) + jniCrossCompileOptions << JniCrossCompileOptions(nativeUtils.wpi.platforms.linuxarm64) + sources { + cpp { + source { + srcDirs 'src/main/driver/cpp' + include '**/*.cpp' + } + exportedHeaders { + srcDirs 'src/main/driver/include' + } + } + } + + nativeUtils.useRequiredLibrary(it, "driver_shared") + } + } + testSuites { + VendorTest { + sources.cpp { + source { + srcDir 'src/test/native/cpp' + include '**/*.cpp' + } + } + + binaries.all { + lib library: 'VendorDriver', linkage: 'shared' + } + + nativeUtils.useRequiredLibrary(it, "wpilib_executable_shared", "googletest_static") + } + + VendorDriverTest { + sources.cpp { + source { + srcDir 'src/test/driver/cpp' + include '**/*.cpp' + } + } + + nativeUtils.useRequiredLibrary(it, "wpilib_executable_shared", "googletest_static") + } + } +} + +apply from: 'publish.gradle' + +wrapper { + gradleVersion '8.5' +} diff --git a/config.gradle b/config.gradle new file mode 100644 index 0000000..0c88342 --- /dev/null +++ b/config.gradle @@ -0,0 +1,177 @@ +import org.gradle.internal.os.OperatingSystem + +nativeUtils.addWpiNativeUtils() +nativeUtils.withCrossRoboRIO() + +nativeUtils { + wpi { + configureDependencies { + wpiVersion = "2024.+" + opencvYear = "frc2024" + googleTestYear = "frc2024" + niLibVersion = "2024.2.1" + opencvVersion = "4.8.0-2" + googleTestVersion = "1.14.0-1" + } + } +} + +nativeUtils.wpi.addWarnings() +nativeUtils.wpi.addWarningsAsErrors() + +nativeUtils.setSinglePrintPerPlatform() + +model { + // Uncomment this, and remove lines below it to enable builds for just roborio + // components { + // all { + // targetPlatform nativeUtils.wpi.platforms.roborio + // } + // } + components { + all { + nativeUtils.useAllPlatforms(it) + } + } + binaries { + withType(NativeBinarySpec).all { + nativeUtils.usePlatformArguments(it) + } + } +} + +ext.appendDebugPathToBinaries = { binaries-> + binaries.withType(StaticLibraryBinarySpec) { + if (it.buildType.name.contains('debug')) { + def staticFileDir = it.staticLibraryFile.parentFile + def staticFileName = it.staticLibraryFile.name + def staticFileExtension = staticFileName.substring(staticFileName.lastIndexOf('.')) + staticFileName = staticFileName.substring(0, staticFileName.lastIndexOf('.')) + staticFileName = staticFileName + 'd' + staticFileExtension + def newStaticFile = new File(staticFileDir, staticFileName) + it.staticLibraryFile = newStaticFile + } + } + binaries.withType(SharedLibraryBinarySpec) { + if (it.buildType.name.contains('debug')) { + def sharedFileDir = it.sharedLibraryFile.parentFile + def sharedFileName = it.sharedLibraryFile.name + def sharedFileExtension = sharedFileName.substring(sharedFileName.lastIndexOf('.')) + sharedFileName = sharedFileName.substring(0, sharedFileName.lastIndexOf('.')) + sharedFileName = sharedFileName + 'd' + sharedFileExtension + def newSharedFile = new File(sharedFileDir, sharedFileName) + + def sharedLinkFileDir = it.sharedLibraryLinkFile.parentFile + def sharedLinkFileName = it.sharedLibraryLinkFile.name + def sharedLinkFileExtension = sharedLinkFileName.substring(sharedLinkFileName.lastIndexOf('.')) + sharedLinkFileName = sharedLinkFileName.substring(0, sharedLinkFileName.lastIndexOf('.')) + sharedLinkFileName = sharedLinkFileName + 'd' + sharedLinkFileExtension + def newLinkFile = new File(sharedLinkFileDir, sharedLinkFileName) + + it.sharedLibraryLinkFile = newLinkFile + it.sharedLibraryFile = newSharedFile + } + } +} + +ext.createComponentZipTasks = { components, names, base, type, project, func -> + def stringNames = names.collect {it.toString()} + def configMap = [:] + components.each { + if (it in NativeLibrarySpec && stringNames.contains(it.name)) { + it.binaries.each { + if (!it.buildable) return + def target = nativeUtils.getPublishClassifier(it) + if (configMap.containsKey(target)) { + configMap.get(target).add(it) + } else { + configMap.put(target, []) + configMap.get(target).add(it) + } + } + } + } + def taskList = [] + def outputsFolder = file("$project.buildDir/outputs") + configMap.each { key, value -> + def task = project.tasks.create(base + "-${key}", type) { + description = 'Creates component archive for platform ' + key + destinationDirectory = outputsFolder + archiveClassifier = key + archiveBaseName = '_M_' + base + duplicatesStrategy = 'exclude' + + from(licenseFile) { + into '/' + } + + func(it, value) + } + taskList.add(task) + + project.build.dependsOn task + + project.artifacts { + task + } + addTaskToCopyAllOutputs(task) + } + return taskList +} + +ext.createAllCombined = { list, name, base, type, project -> + def outputsFolder = file("$project.buildDir/outputs") + + def task = project.tasks.create(base + "-all", type) { + description = "Creates component archive for all classifiers" + destinationDirectory = outputsFolder + classifier = "all" + archiveBaseName = base + duplicatesStrategy = 'exclude' + + list.each { + if (it.name.endsWith('debug')) return + from project.zipTree(it.archiveFile) + dependsOn it + } + } + + project.build.dependsOn task + + project.artifacts { + task + } + + return task + +} + +ext.includeStandardZipFormat = { task, value -> + value.each { binary -> + if (binary.buildable) { + if (binary instanceof SharedLibraryBinarySpec) { + task.dependsOn binary.tasks.link + task.from(new File(binary.sharedLibraryFile.absolutePath + ".debug")) { + into nativeUtils.getPlatformPath(binary) + '/shared' + } + def sharedPath = binary.sharedLibraryFile.absolutePath + sharedPath = sharedPath.substring(0, sharedPath.length() - 4) + + task.from(new File(sharedPath + '.pdb')) { + into nativeUtils.getPlatformPath(binary) + '/shared' + } + task.from(binary.sharedLibraryFile) { + into nativeUtils.getPlatformPath(binary) + '/shared' + } + task.from(binary.sharedLibraryLinkFile) { + into nativeUtils.getPlatformPath(binary) + '/shared' + } + } else if (binary instanceof StaticLibraryBinarySpec) { + task.dependsOn binary.tasks.createStaticLib + task.from(binary.staticLibraryFile) { + into nativeUtils.getPlatformPath(binary) + '/static' + } + } + } + } +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..d64cd49 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..1af9e09 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..1aa94a4 --- /dev/null +++ b/gradlew @@ -0,0 +1,249 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# 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 +# +# https://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. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..6689b85 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/publish.gradle b/publish.gradle new file mode 100644 index 0000000..477455a --- /dev/null +++ b/publish.gradle @@ -0,0 +1,205 @@ +apply plugin: 'maven-publish' + +ext.licenseFile = files("$rootDir/LICENSE.txt") + +def pubVersion = '0.0.1' + +def outputsFolder = file("$buildDir/outputs") + +def versionFile = file("$outputsFolder/version.txt") + +task outputVersions() { + description = 'Prints the versions of wpilib to a file for use by the downstream packaging project' + group = 'Build' + outputs.files(versionFile) + + doFirst { + buildDir.mkdir() + outputsFolder.mkdir() + } + + doLast { + versionFile.write pubVersion + } +} + +task libraryBuild() {} + +build.dependsOn outputVersions + +task copyAllOutputs(type: Copy) { + destinationDir file("$buildDir/allOutputs") + from versionFile + dependsOn outputVersions +} + +build.dependsOn copyAllOutputs +copyAllOutputs.dependsOn outputVersions + +ext.addTaskToCopyAllOutputs = { task -> + copyAllOutputs.dependsOn task + copyAllOutputs.inputs.file task.archiveFile + copyAllOutputs.from task.archiveFile +} + +def artifactGroupId = 'com.vendor.frc' +def baseArtifactId = 'Vendor' +def driverZipBaseName = "_GROUP_com_vendor_frc_ID_${baseArtifactId}-driver_CLS" +def zipBaseName = "_GROUP_com_vendor_frc_ID_${baseArtifactId}-cpp_CLS" +def javaBaseName = "_GROUP_com_vendor_frc_ID_${baseArtifactId}-java_CLS" + +task cppHeadersZip(type: Zip) { + destinationDirectory = outputsFolder + archiveBaseName = zipBaseName + archiveClassifier = "headers" + + from(licenseFile) { + into '/' + } + + from('src/main/native/include') { + into '/' + } +} + +task cppSourceZip(type: Zip) { + destinationDirectory = outputsFolder + archiveBaseName = zipBaseName + archiveClassifier = "sources" + + from(licenseFile) { + into '/' + } + + from('src/main/native/cpp') { + into '/' + } +} + +task cppDriverHeadersZip(type: Zip) { + destinationDirectory = outputsFolder + archiveBaseName = driverZipBaseName + archiveClassifier = "headers" + + from(licenseFile) { + into '/' + } + + from('src/main/driver/include') { + into '/' + } +} + +build.dependsOn cppHeadersZip +addTaskToCopyAllOutputs(cppHeadersZip) +build.dependsOn cppSourceZip +addTaskToCopyAllOutputs(cppSourceZip) +build.dependsOn cppDriverHeadersZip +addTaskToCopyAllOutputs(cppDriverHeadersZip) + +task sourcesJar(type: Jar, dependsOn: classes) { + archiveClassifier = 'sources' + from sourceSets.main.allSource +} + +task javadocJar(type: Jar, dependsOn: javadoc) { + archiveClassifier = 'javadoc' + from javadoc.destinationDir +} + +task outputJar(type: Jar, dependsOn: classes) { + archiveBaseName = javaBaseName + destinationDirectory = outputsFolder + from sourceSets.main.output +} + +task outputSourcesJar(type: Jar, dependsOn: classes) { + archiveBaseName = javaBaseName + destinationDirectory = outputsFolder + archiveClassifier = 'sources' + from sourceSets.main.allSource +} + +task outputJavadocJar(type: Jar, dependsOn: javadoc) { + archiveBaseName = javaBaseName + destinationDirectory = outputsFolder + archiveClassifier = 'javadoc' + from javadoc.destinationDir +} + +artifacts { + archives sourcesJar + archives javadocJar + archives outputJar + archives outputSourcesJar + archives outputJavadocJar +} + +addTaskToCopyAllOutputs(outputSourcesJar) +addTaskToCopyAllOutputs(outputJavadocJar) +addTaskToCopyAllOutputs(outputJar) + +build.dependsOn outputSourcesJar +build.dependsOn outputJavadocJar +build.dependsOn outputJar + +libraryBuild.dependsOn build + +def releasesRepoUrl = "$buildDir/repos/releases" + +publishing { + repositories { + maven { + + url = releasesRepoUrl + } + } +} + +task cleanReleaseRepo(type: Delete) { + delete releasesRepoUrl +} + +tasks.matching {it != cleanReleaseRepo}.all {it.dependsOn cleanReleaseRepo} + +model { + publishing { + def taskList = createComponentZipTasks($.components, ['Vendor'], zipBaseName, Zip, project, includeStandardZipFormat) + + def driverTaskList = createComponentZipTasks($.components, ['VendorDriver'], driverZipBaseName, Zip, project, includeStandardZipFormat) + + publications { + cpp(MavenPublication) { + taskList.each { + artifact it + } + artifact cppHeadersZip + artifact cppSourceZip + + artifactId = "${baseArtifactId}-cpp" + groupId artifactGroupId + version pubVersion + } + driver(MavenPublication) { + driverTaskList.each { + artifact it + } + artifact cppDriverHeadersZip + + artifactId = "${baseArtifactId}-driver" + groupId artifactGroupId + version pubVersion + } + + java(MavenPublication) { + artifact jar + artifact sourcesJar + artifact javadocJar + + artifactId = "${baseArtifactId}-java" + groupId artifactGroupId + version pubVersion + } + } + } +} diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..588dcaf --- /dev/null +++ b/settings.gradle @@ -0,0 +1,6 @@ +pluginManagement { + repositories { + mavenLocal() + gradlePluginPortal() + } +} \ No newline at end of file diff --git a/src/main/driver/cpp/VendorJNI.cpp b/src/main/driver/cpp/VendorJNI.cpp new file mode 100644 index 0000000..0a3ce66 --- /dev/null +++ b/src/main/driver/cpp/VendorJNI.cpp @@ -0,0 +1,22 @@ +#include "jni.h" +#include "com_vendor_jni_VendorJNI.h" + +JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) { + // Check to ensure the JNI version is valid + + JNIEnv* env; + if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) != JNI_OK) + return JNI_ERR; + + // In here is also where you store things like class references + // if they are ever needed + + return JNI_VERSION_1_6; +} + +JNIEXPORT void JNICALL JNI_OnUnload(JavaVM* vm, void* reserved) {} + +JNIEXPORT jint JNICALL Java_com_vendor_jni_VendorJNI_initialize + (JNIEnv *, jclass) { + return 0; +} diff --git a/src/main/driver/cpp/driversource.cpp b/src/main/driver/cpp/driversource.cpp new file mode 100644 index 0000000..15716a4 --- /dev/null +++ b/src/main/driver/cpp/driversource.cpp @@ -0,0 +1,7 @@ +#include "driverheader.h" + +extern "C" { +void c_doThing() { + +} +} diff --git a/src/main/driver/include/driverheader.h b/src/main/driver/include/driverheader.h new file mode 100644 index 0000000..5aeff00 --- /dev/null +++ b/src/main/driver/include/driverheader.h @@ -0,0 +1,11 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +void c_doThing(); + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/src/main/driver/symbols.txt b/src/main/driver/symbols.txt new file mode 100644 index 0000000..2f9b641 --- /dev/null +++ b/src/main/driver/symbols.txt @@ -0,0 +1,4 @@ +JNI_OnLoad +JNI_OnUnload +Java_com_vendor_jni_VendorJNI_initialize +c_doThing diff --git a/src/main/java/com/vendor/jni/VendorJNI.java b/src/main/java/com/vendor/jni/VendorJNI.java new file mode 100644 index 0000000..9ee6fe1 --- /dev/null +++ b/src/main/java/com/vendor/jni/VendorJNI.java @@ -0,0 +1,60 @@ +package com.vendor.jni; + +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Demo class for loading the driver via JNI. + */ +public class VendorJNI { + static boolean libraryLoaded = false; + + /** + * Helper class for determining whether or not to load the driver on static initialization. + */ + public static class Helper { + private static AtomicBoolean extractOnStaticLoad = new AtomicBoolean(true); + + /** + * Get whether to load the driver on static init. + * @return true if the driver will load on static init + */ + public static boolean getExtractOnStaticLoad() { + return extractOnStaticLoad.get(); + } + + /** + * Set whether to load the driver on static init. + * @param load the new value + */ + public static void setExtractOnStaticLoad(boolean load) { + extractOnStaticLoad.set(load); + } + } + + static { + if (Helper.getExtractOnStaticLoad()) { + System.loadLibrary("VendorDriver"); + libraryLoaded = true; + } + } + + /** + * Force load the library. + */ + public static synchronized void forceLoad() { + if (libraryLoaded) { + return; + } + System.loadLibrary("VendorDriver"); + libraryLoaded = true; + } + + /** + * Tells the driver to initialize. + * This is a demo of a native JNI method from the driver. + * + * @return the int returned by the driver + * @see "VendorJNI.cpp" + */ + public static native int initialize(); +} diff --git a/src/main/native/cpp/source.cpp b/src/main/native/cpp/source.cpp new file mode 100644 index 0000000..3990d40 --- /dev/null +++ b/src/main/native/cpp/source.cpp @@ -0,0 +1,6 @@ +#include "header.h" +#include "driverheader.h" + +void func() { + c_doThing(); +} diff --git a/src/main/native/include/header.h b/src/main/native/include/header.h new file mode 100644 index 0000000..e69de29 diff --git a/src/test/driver/cpp/main.cpp b/src/test/driver/cpp/main.cpp new file mode 100644 index 0000000..d0e7d22 --- /dev/null +++ b/src/test/driver/cpp/main.cpp @@ -0,0 +1,6 @@ +#include "gtest/gtest.h" + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/src/test/native/cpp/main.cpp b/src/test/native/cpp/main.cpp new file mode 100644 index 0000000..d0e7d22 --- /dev/null +++ b/src/test/native/cpp/main.cpp @@ -0,0 +1,6 @@ +#include "gtest/gtest.h" + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +}