diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..a3ab7bb --- /dev/null +++ b/.clang-format @@ -0,0 +1,166 @@ +--- +Language: Cpp +BasedOnStyle: Google +AccessModifierOffset: -1 +AlignAfterOpenBracket: Align +AlignConsecutiveMacros: false +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Left +AlignOperands: true +AlignTrailingComments: true +AllowAllArgumentsOnNextLine: true +AllowAllConstructorInitializersOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: Inline +AllowShortLambdasOnASingleLine: All +AllowShortIfStatementsOnASingleLine: Never +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: true +AlwaysBreakTemplateDeclarations: Yes +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Attach +BreakBeforeInheritanceComma: false +BreakInheritanceList: BeforeColon +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeColon +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 80 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DeriveLineEnding: true +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IncludeBlocks: Regroup +IncludeCategories: + - Regex: '^' + Priority: 2 + SortPriority: 0 + - Regex: '^<.*\.h>' + Priority: 1 + SortPriority: 0 + - Regex: '^<.*' + Priority: 2 + SortPriority: 0 + - Regex: '.*' + Priority: 3 + SortPriority: 0 +IncludeIsMainRegex: '([-_](test|unittest))?$' +IncludeIsMainSourceRegex: '' +IndentCaseLabels: true +IndentGotoLabels: true +IndentPPDirectives: None +IndentWidth: 4 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Never +ObjCBlockIndentWidth: 2 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 1 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 200 +PointerAlignment: Left +RawStringFormats: + - Language: Cpp + Delimiters: + - cc + - CC + - cpp + - Cpp + - CPP + - 'c++' + - 'C++' + CanonicalDelimiter: '' + BasedOnStyle: google + - Language: TextProto + Delimiters: + - pb + - PB + - proto + - PROTO + EnclosingFunctions: + - EqualsProto + - EquivToProto + - PARSE_PARTIAL_TEXT_PROTO + - PARSE_TEST_PROTO + - PARSE_TEXT_PROTO + - ParseTextOrDie + - ParseTextProtoOrDie + CanonicalDelimiter: '' + BasedOnStyle: google +ReflowComments: true +SortIncludes: false +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 2 +SpacesInAngles: false +SpacesInConditionalStatement: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +SpaceBeforeSquareBrackets: false +Standard: c++17 +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 8 +UseTab: Never +... diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..08471a3 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,73 @@ +name: Build + +on: [pull_request, push, workflow_dispatch] + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} + cancel-in-progress: true + +# env: +# SCCACHE_WEBDAV_ENDPOINT: "https://frcmaven.wpi.edu/artifactory/wpilib-generic-cache-cmake-local" +# SCCACHE_WEBDAV_KEY_PREFIX: "sccache" + +jobs: + build: + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-24.04 + name: Linux + runtests: true + container: wpilib/ubuntu-base:24.04 + preset: default + builddir: build + flags: "" + - os: ubuntu-24.04 + name: Linux MRC + runtests: false + container: wpilib/systemcore-cross-ubuntu:2025-24.04 + builddir: buildmrc + flags: "-DCMAKE_TOOLCHAIN_FILE=/usr/local/toolchain-config.cmake" + preset: mrc + + name: "Build - ${{ matrix.name }}" + runs-on: ${{ matrix.os }} + container: ${{ matrix.container }} + steps: + - name: Install dependencies (Linux) + if: runner.os == 'Linux' + run: sudo apt-get update && sudo apt-get install -y ninja-build + + - run: git config --global --add safe.directory "$PWD" + + # - name: Install sccache + # uses: mozilla-actions/sccache-action@v0.0.5 + + - name: Checkout repository + uses: actions/checkout@v4 + with: + submodules: 'recursive' + + - name: configure + run: cmake --preset ${{ matrix.preset }} -DCMAKE_C_COMPILER_LAUNCHER=sccache -DCMAKE_CXX_COMPILER_LAUNCHER=sccache ${{ matrix.flags }} + # env: + # SCCACHE_WEBDAV_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }} + # SCCACHE_WEBDAV_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }} + + - name: build + run: cmake --build --preset ${{ matrix.preset }} --parallel $(nproc) + # env: + # SCCACHE_WEBDAV_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }} + # SCCACHE_WEBDAV_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }} + + - name: install + run: cmake --install ${{ matrix.builddir }} --prefix install/${{ matrix.builddir }} + # env: + # SCCACHE_WEBDAV_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }} + # SCCACHE_WEBDAV_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }} + + - name: test + if: matrix.runtests == true + working-directory: build + run: ctest --output-on-failure diff --git a/.github/workflows/sanitizers.yml b/.github/workflows/sanitizers.yml new file mode 100644 index 0000000..5e4810f --- /dev/null +++ b/.github/workflows/sanitizers.yml @@ -0,0 +1,59 @@ +name: Sanitizers + +on: [pull_request, push] + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} + cancel-in-progress: true + +# env: +# SCCACHE_WEBDAV_ENDPOINT: "https://frcmaven.wpi.edu/artifactory/wpilib-generic-cache-cmake-local" +# SCCACHE_WEBDAV_KEY_PREFIX: "sccache" + +jobs: + build: + strategy: + fail-fast: false + matrix: + include: + - name: asan + cmake-flags: "-DCMAKE_BUILD_TYPE=Asan" + ctest-env: "" + - name: tsan + cmake-flags: "-DCMAKE_BUILD_TYPE=Tsan" + ctest-env: "TSAN_OPTIONS=second_deadlock_stack=1" + - name: ubsan + cmake-flags: "-DCMAKE_BUILD_TYPE=Ubsan" + ctest-env: "" + ctest-flags: "" + name: "${{ matrix.name }}" + runs-on: ubuntu-24.04 + container: wpilib/ubuntu-base:24.04 + steps: + - name: Install Dependencies + run: sudo apt-get update && sudo apt-get install -y clang-18 ninja-build + + # - name: Install sccache + # uses: mozilla-actions/sccache-action@v0.0.5 + + - run: git config --global --add safe.directory "$PWD" + + - uses: actions/checkout@v4 + with: + submodules: 'recursive' + + - name: configure + run: cmake --preset default -DCMAKE_C_COMPILER_LAUNCHER=sccache -DCMAKE_CXX_COMPILER_LAUNCHER=sccache -DCMAKE_C_COMPILER:FILEPATH=/usr/bin/clang-14 -DCMAKE_CXX_COMPILER:FILEPATH=/usr/bin/clang++-14 ${{ matrix.cmake-flags }} + # env: + # SCCACHE_WEBDAV_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }} + # SCCACHE_WEBDAV_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }} + + - name: build + run: cmake --build --preset default --parallel $(nproc) + # env: + # SCCACHE_WEBDAV_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }} + # SCCACHE_WEBDAV_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }} + + - name: test + working-directory: build + run: ${{ matrix.ctest-env }} ctest --output-on-failure diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b62e95f --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +build/ +buildmrc/ +buildninja/ +vcpkg_installed/ +*.wpilog +networktables.json +builddroidarm/ +builddroidx64/ +install/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..4481114 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "allwpilib"] + path = allwpilib + url = https://github.com/wpilibsuite/allwpilib diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..168cc9a --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,22 @@ +{ + "configurations": [ + { + "name": "Win32", + "includePath": [ + "${workspaceFolder}/**" + ], + "defines": [ + "_DEBUG", + "UNICODE", + "_UNICODE" + ], + "windowsSdkVersion": "10.0.26100.0", + "compilerPath": "cl.exe", + "cStandard": "c17", + "cppStandard": "c++17", + "intelliSenseMode": "windows-msvc-x64", + "compileCommands": "${workspaceFolder}/build/compile_commands.json" + } + ], + "version": 4 +} diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..ed94b69 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,93 @@ +# Disable in-source builds to prevent source tree corruption. +if(" ${CMAKE_CURRENT_SOURCE_DIR}" STREQUAL " ${CMAKE_CURRENT_BINARY_DIR}") + message( + FATAL_ERROR + " +FATAL: In-source builds are not allowed. + You should create a separate directory for build files. +" + ) +endif() + +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +cmake_minimum_required(VERSION 3.21) + +project(scservices) + +# Set default build type to release with debug info (i.e. release mode optimizations +# are performed, but debug info still exists). +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE "RelWithDebInfo" CACHE STRING "" FORCE) +endif() + +option(BUILD_SHARED_LIBS "Build with shared libs (needed for JNI)" OFF) + +set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") +set(CMAKE_POSITION_INDEPENDENT_CODE ON) + +option(WITH_NTCORE "Include ntcore" ON) +option(WITH_JAVA "Include java and JNI in the build" OFF) +option(WITH_CSCORE "Build cscore (needs OpenCV)" OFF) +option(WITH_WPIMATH "Build wpimath" OFF) +option(WITH_WPILIB "Build hal, wpilibc/j, and myRobot (needs OpenCV)" OFF) +option(WITH_OLD_COMMANDS "Build old commands" OFF) +option(WITH_EXAMPLES "Build examples" OFF) +option(WITH_TESTS "Build unit tests (requires internet connection)" OFF) +option(WITH_GUI "Build GUI items" OFF) +option(WITH_SIMULATION_MODULES "Build simulation modules" OFF) +option(WITH_PROTOBUF "Build protobuf support" OFF) + +include(${PROJECT_SOURCE_DIR}/cmake/GitCommands.cmake) +get_git_current_hash(${PROJECT_SOURCE_DIR} BUILD_CURRENT_GIT_HASH) + +if(NOT BUILD_CURRENT_GIT_HASH) + message("Failed to get git hash. Binary will not contain git hash") + set(GIT_CURRENT_HASH "Unknown") + set(BUILD_CURRENT_GIT_HASH "Unknown") +endif() + +string(TIMESTAMP BUILD_CURRENT_TIMESTAMP UTC) + +if (MSVC) + if ("${CMAKE_C_COMPILER_VERSION}" VERSION_GREATER_EQUAL "19.20") + include(${PROJECT_SOURCE_DIR}/cmake/SourceLink.cmake) + file(TO_NATIVE_PATH "${PROJECT_BINARY_DIR}/source_link.json" SOURCE_LINK_JSON) + file(TO_NATIVE_PATH "${PROJECT_SOURCE_DIR}/cmake/source_link.json.in" SOURCE_LINK_JSON_INPUT) + source_link(${PROJECT_SOURCE_DIR} ${SOURCE_LINK_JSON} ${SOURCE_LINK_JSON_INPUT}) + set(CMAKE_EXE_LINKER_FLAGS_DEBUG "${CMAKE_EXE_LINKER_FLAGS_DEBUG} /INCREMENTAL:NO") + set(CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO} /INCREMENTAL:NO") + set(CMAKE_SHARED_LINKER_FLAGS_DEBUG "${CMAKE_SHARED_LINKER_FLAGS_DEBUG} /INCREMENTAL:NO") + set(CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO} /INCREMENTAL:NO") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /FORCE:PGOREPRO /SOURCELINK:\"${SOURCE_LINK_JSON}\"") + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /FORCE:PGOREPRO /SOURCELINK:\"${SOURCE_LINK_JSON}\"") + else() + message(WARNING "Disabling SourceLink due to old version of MSVC. Please update to VS2019 or greater!") + endif() +endif() + +add_subdirectory(allwpilib EXCLUDE_FROM_ALL) + +set(BUILD_SHARED_LIBS OFF) + +set (CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set (CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) + +set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS FALSE) + +Include(FetchContent) + +FetchContent_Declare( + Catch2 + GIT_REPOSITORY https://github.com/catchorg/Catch2.git + GIT_TAG v3.7.1 # or a later release +) + +FetchContent_MakeAvailable(Catch2) +list(APPEND CMAKE_MODULE_PATH ${catch2_SOURCE_DIR}/extras) +include(CTest) +include(Catch) + +add_subdirectory(common) + +add_subdirectory(radio) diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 0000000..6735e9a --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,48 @@ +{ + "version": 3, + "configurePresets": [ + { + "name": "common", + "hidden": true, + "binaryDir": "${sourceDir}/build", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "RelWithDebInfo" + } + }, + { + "name": "msvc", + "inherits": "common", + "generator": "Visual Studio 17 2022" + }, + { + "name": "default", + "inherits": "common", + "generator": "Ninja" + }, + { + "name": "mrc", + "binaryDir": "${sourceDir}/buildmrc", + "inherits": "default", + "cacheVariables": { + "MRC_BUILD": true + } + } + ], + "buildPresets": [ + { + "name": "msvc", + "configurePreset": "msvc", + "configuration": "RelWithDebInfo" + }, + { + "name": "default", + "configurePreset": "default", + "configuration": "RelWithDebInfo" + }, + { + "name": "mrc", + "configurePreset": "mrc", + "configuration": "RelWithDebInfo" + } + ] + } diff --git a/Launch-VsDevShell.ps1 b/Launch-VsDevShell.ps1 new file mode 100644 index 0000000..3cba71b --- /dev/null +++ b/Launch-VsDevShell.ps1 @@ -0,0 +1,277 @@ +<# +.SYNOPSIS Launch Developer PowerShell +.DESCRIPTION +Locates and imports a Developer PowerShell module and calls the Enter-VsDevShell cmdlet. The Developer PowerShell module +is located in one of several ways: + 1) From a path in a Visual Studio installation + 2) From the latest installation of Visual Studio (higher versions first) + 3) From the instance ID of a Visual Studio installation + 4) By selecting a Visual Studio installation from a list + +By default, with no parameters, the path to this script is used to locate the Developer PowerShell module. If that fails, +then the latest Visual Studio installation is used. If both methods fail, then the user can select a Visual Studio installation +from a list. +.PARAMETER VsInstallationPath +A path in a Visual Studio installation. The path is used to locate the Developer PowerShell module. +By default, this is the path to this script. +.PARAMETER Latest +Use the latest Visual Studio installation to locate the Developer PowerShell module. +.PARAMETER List +Display a list of Visual Studio installations to choose from. The choosen installation is used to locate the Developer PowerShell module. +.PARAMETER VsInstanceId +A Visual Studio installation instance ID. The matching installation is used to locate the Developer PowerShell module. +.PARAMETER ExcludePrerelease +Excludes Prerelease versions of Visual Studio from consideration. Applies only to Latest and List. +.PARAMETER VsWherePath +Path to the vswhere utility used to located and identify Visual Studio installations. +By default, the path is the well-known location shared by Visual Studio installations. +#> +[CmdletBinding(DefaultParameterSetName = "Default")] +param ( + [ValidateScript({Test-Path $_})] + [Parameter(ParameterSetName = "VsInstallationPath")] + [string] + $VsInstallationPath = "$($MyInvocation.MyCommand.Definition)", + + [Parameter(ParameterSetName = "Latest")] + [switch] + $Latest, + + [Parameter(ParameterSetName = "List")] + [switch] + $List, + + [Parameter(ParameterSetName = "List")] + [object[]] + $DisplayProperties = @("displayName", "instanceId", "installationVersion", "isPrerelease", "installationName", "installDate"), + + [Parameter(ParameterSetName = "VsInstanceId", Mandatory = $true)] + [string] + $VsInstanceId, + + [Parameter(ParameterSetName = "Latest")] + [Parameter(ParameterSetName = "List")] + [switch] + $ExcludePrerelease, + + [Parameter(ParameterSetName = "Default")] + [Parameter(ParameterSetName = "VsInstallationPath")] + [Parameter(ParameterSetName = "Latest")] + [Parameter(ParameterSetName = "List")] + [Parameter(ParameterSetName = "VsInstanceId")] + [ValidateSet('x86','amd64','arm','arm64')] + [string] + $Arch, + + [Parameter(ParameterSetName = "Default")] + [Parameter(ParameterSetName = "VsInstallationPath")] + [Parameter(ParameterSetName = "Latest")] + [Parameter(ParameterSetName = "List")] + [Parameter(ParameterSetName = "VsInstanceId")] + [ValidateSet('x86','amd64')] + [string] + $HostArch, + + [Parameter(ParameterSetName = "Default")] + [Parameter(ParameterSetName = "VsInstallationPath")] + [Parameter(ParameterSetName = "Latest")] + [Parameter(ParameterSetName = "List")] + [Parameter(ParameterSetName = "VsInstanceId")] + [switch] + $SkipAutomaticLocation, + + [ValidateScript({Test-Path $_ -PathType 'Leaf'})] + [Parameter(ParameterSetName = "Default")] + [Parameter(ParameterSetName = "VsInstallationPath")] + [Parameter(ParameterSetName = "Latest")] + [Parameter(ParameterSetName = "List")] + [Parameter(ParameterSetName = "VsInstanceId")] + [string] + $VsWherePath = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" +) + +function GetSetupConfigurations { + param ( + $whereArgs + ) + + $expression = "& `"$VsWherePath`" $whereArgs -format json" + Invoke-Expression $expression | ConvertFrom-Json +} + +function LaunchDevShell { + param ( + $config + ) + + $basePath = $config.installationPath + $instanceId = $config.instanceId + + $currModulePath = "$basePath\Common7\Tools\Microsoft.VisualStudio.DevShell.dll" + # Prior to 16.3 the DevShell module was in a different location + $prevModulePath = "$basePath\Common7\Tools\vsdevshell\Microsoft.VisualStudio.DevShell.dll" + + $modulePath = if (Test-Path $prevModulePath) { $prevModulePath } else { $currModulePath } + + if (Test-Path $modulePath) { + Write-Verbose "Found at $modulePath." + + try { + Import-Module $modulePath + } + catch [System.IO.FileLoadException] { + Write-Verbose "The module has already been imported from a different installation of Visual Studio:" + (Get-Module Microsoft.VisualStudio.DevShell).Path | Write-Verbose + } + + $params = @{ + VsInstanceId = $instanceId + } + + $command = Get-Command Enter-VsDevShell + + $params.SkipAutomaticLocation = $true + + # -Arch is only available from 17.1 + if ($Arch -and $command.Parameters.ContainsKey("Arch")) + { + $params.Arch = $Arch + } else { + $params.Arch = "amd64" + } + + # -HostArch is only available from 17.1 + if ($HostArch -and $command.Parameters.ContainsKey("HostArch")) + { + $params.HostArch = $HostArch + } else { + $params.HostArch = "amd64" + } + + # -ReportNewInstanceType is only available from 16.5 + if ($command.Parameters.ContainsKey("ReportNewInstanceType")) { + $params.ReportNewInstanceType = "LaunchScript" + } + + $boundParams = $PSCmdlet.MyInvocation.BoundParameters + + if ($boundParams.ContainsKey("Verbose") -and + $boundParams["Verbose"].IsPresent) + { + Write-Verbose "Enter-VsDevShell Parameters:" + $params.GetEnumerator() | ForEach-Object{ + $message = '{0} = {1}' -f $_.key, $_.value + Write-Verbose $message + } + } + + Enter-VsDevShell @params + exit + } + + throw [System.Management.Automation.ErrorRecord]::new( + [System.Exception]::new("Required assembly could not be located. This most likely indicates an installation error. Try repairing your Visual Studio installation. Expected location: $modulePath"), + "DevShellModuleLoad", + [System.Management.Automation.ErrorCategory]::NotInstalled, + $config) +} + +function VsInstallationPath { + $setupargs = "-path `"$VsInstallationPath`"" + + Write-Verbose "Using path: $VsInstallationPath" + $config = GetSetupConfigurations($setupargs) + LaunchDevShell($config) +} + +function Latest { + $setupargs = "-latest" + + if (-not $ExcludePrerelease) { + $setupargs += " -prerelease" + } + + $config = GetSetupConfigurations($setupargs) + LaunchDevShell($config) +} + +function VsInstanceId { + $configs = GetSetupConfigurations("-prerelease -all") + $config = $configs | Where-Object { $_.instanceId -eq $VsInstanceId } + if ($config) { + Write-Verbose "Found Visual Studio installation with InstanceId of '$($config.instanceId)' and InstallationPath '$($config.installationPath)'" + LaunchDevShell($config) + exit + } + + throw [System.Management.Automation.ErrorRecord]::new( + [System.Exception]::new("Could not find an installation of Visual Studio with InstanceId '$VsInstanceId'."), + "VsSetupInstance", + [System.Management.Automation.ErrorCategory]::InvalidArgument, + $config) +} + +function List { + $setupargs = "-sort" + + if (-not $ExcludePrerelease) { + $setupargs = " -prerelease" + } + + $configs = GetSetupConfigurations($setupargs) + + $DisplayProperties = @("#") + $DisplayProperties + + # Add an incrementing select column + $configs = $configs | + Sort-Object displayName, installationDate | + ForEach-Object {$i = 0}{ $i++; $_ | Add-Member -NotePropertyName "#" -NotePropertyValue $i -PassThru } + + Write-Host "The following Visual Studio installations were found:" + $configs | Format-Table -Property $DisplayProperties + + $selected = Read-Host "Enter '#' of the Visual Studio installation to launch DevShell. to quit: " + if (-not $selected) { exit } + + $config = $configs | Where-Object { $_."#" -eq $selected } + + if ($config) { + LaunchDevShell($config) + } + else { + "Invalid selection: $selected" + } +} + +function Default{ + Write-Verbose "No parameters passed to script. Trying VsInstallationPath." + + try { + VsInstallationPath + exit + } + catch { + Write-Verbose "VsInstallationPath failed. Trying Latest." + } + + Write-Host "Could not start Developer PowerShell using the script path." + Write-Host "Attempting to launch from the latest Visual Studio installation." + + try { + Latest + exit + } + catch { + Write-Verbose "Latest failed. Defaulting to List." + } + + Write-Host "Could not start Developer PowerShell from the latest Visual Studio installation." + Write-Host + + List +} + +if ($PSCmdlet.ParameterSetName) { + & (Get-ChildItem "Function:$($PSCmdlet.ParameterSetName)") + exit +} diff --git a/allwpilib b/allwpilib new file mode 160000 index 0000000..6ba7189 --- /dev/null +++ b/allwpilib @@ -0,0 +1 @@ +Subproject commit 6ba7189373714e5fdc84c95308e5a0d6caa3e4f4 diff --git a/cmake/GitCommands.cmake b/cmake/GitCommands.cmake new file mode 100644 index 0000000..9e2fc72 --- /dev/null +++ b/cmake/GitCommands.cmake @@ -0,0 +1,87 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +find_package(Git QUIET) +if (NOT Git_FOUND) + message(STATUS "Unable to find git, which is needed for versioning") +endif() + +function(get_git_dir DIRECTORY OUTPUT_VAR) + execute_process( + COMMAND + ${GIT_EXECUTABLE} rev-parse --git-dir + WORKING_DIRECTORY + ${DIRECTORY} + RESULT_VARIABLE + GIT_DIR_RESULT + OUTPUT_VARIABLE + GIT_DIR_OUTPUT + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + + # Allow to fail + set(${OUTPUT_VAR} ${GIT_DIR_OUTPUT} PARENT_SCOPE) +endfunction() + +function(get_git_current_hash DIRECTORY OUTPUT_VAR) + execute_process( + COMMAND + ${GIT_EXECUTABLE} rev-parse --verify HEAD + WORKING_DIRECTORY + ${DIRECTORY} + RESULT_VARIABLE + GIT_CURRENT_HASH_RESULT + OUTPUT_VARIABLE + GIT_CURRENT_HASH_OUTPUT + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + + if (NOT ("${GIT_CURRENT_HASH_RESULT}" STREQUAL "0")) + message(STATUS ${GIT_CURRENT_HASH_OUTPUT}) + message(STATUS "Failed to get ${DIRECTORY} git hash") + else() + set(${OUTPUT_VAR} ${GIT_CURRENT_HASH_OUTPUT} PARENT_SCOPE) + endif() +endfunction() + +function(get_git_remote_url DIRECTORY OUTPUT_VAR) + execute_process( + COMMAND + ${GIT_EXECUTABLE} config --get remote.origin.url + RESULT_VARIABLE + GIT_REMOTE_URL_RESULT + OUTPUT_VARIABLE + GIT_REMOTE_URL_OUTPUT + OUTPUT_STRIP_TRAILING_WHITESPACE + WORKING_DIRECTORY + ${DIRECTORY} + ) + + if (NOT ("${GIT_REMOTE_URL_RESULT}" STREQUAL "0")) + message(${GIT_REMOTE_URL_OUTPUT}) + message(FATAL_ERROR "Failed to get ${DIRECTORY} git remote") + endif() + + set(${OUTPUT_VAR} ${GIT_REMOTE_URL_OUTPUT} PARENT_SCOPE) +endfunction() + +function(run_git_submodule_foreach CMD DIRECTORY OUTPUT_VALUE) + execute_process( + COMMAND + ${GIT_EXECUTABLE} submodule foreach --quiet --recursive "${CMD}" + RESULT_VARIABLE + GIT_SUBMODULE_CMD_RESULT + OUTPUT_VARIABLE + GIT_SUBMODULE_CMD_OUTPUT + OUTPUT_STRIP_TRAILING_WHITESPACE + WORKING_DIRECTORY + ${DIRECTORY} + ) + + if (NOT ("${GIT_SUBMODULE_CMD_RESULT}" STREQUAL "0")) + message(${GIT_SUBMODULE_CMD_OUTPUT}) + message(FATAL_ERROR "Failed to run git submodule foreach command: ${CMD} in ${DIRECTORY}") + endif() + set(${OUTPUT_VALUE} ${GIT_SUBMODULE_CMD_OUTPUT} PARENT_SCOPE) +endfunction() diff --git a/cmake/SourceLink.cmake b/cmake/SourceLink.cmake new file mode 100644 index 0000000..46395a6 --- /dev/null +++ b/cmake/SourceLink.cmake @@ -0,0 +1,106 @@ +# CMake Module to generate Source Link JSON for the MSVC compiler +# +# Microsoft defines Source Link as the following: +# +# > Source Link is a developer productivity feature that allows unique +# > information about an assembly's original source code to be embedded in its +# > PDB during compilation. +# https://github.com/dotnet/designs/blob/master/accepted/diagnostics/source-link.md +# +# Specifically, this script will embedded information into the PDB of where to +# download the source code from. This will allow developers to use the PDB without +# the source located on disk. +# +# This script currently only works with GitHub but could be extended to support +# other source control servers. Any server which hosts their code as raw source +# over HTTP should work. +# +include(${CMAKE_CURRENT_LIST_DIR}/GitCommands.cmake) + +# Warn if this is included and the compilier doesn't support source link +if ("${CMAKE_C_COMPILER_ID}" STREQUAL "MSVC") + if ("${CMAKE_C_COMPILER_VERSION}" VERSION_GREATER_EQUAL "19.20") + # Good to go! + elseif("${CMAKE_C_COMPILER_VERSION}" VERSION_GREATER_EQUAL "19.14") + message(STATUS "SourceLink enabled but case insensative") + else() + message(WARNING "SourceLink will not work on version of MSVC less than 19.14") + endif() +else() + message(WARNING "SourceLink will not work on the ${CMAKE_C_COMPILER_ID} compiler") +endif() + +# REPO_ROOT is the path to the repository where code it stored. +# +# SOURCE_LINK_JSON_PATH is the file to output the json +function(source_link REPO_ROOT SOURCE_LINK_JSON_PATH SOURCE_LINK_JSON_INPUT_PATH) + if (NOT (IS_DIRECTORY ${REPO_ROOT})) + message(FATAL_ERROR "\"${REPO_ROOT}\" is not a directory") + endif() + + get_git_remote_url(${REPO_ROOT} GIT_REMOTE) + get_git_current_hash(${REPO_ROOT} GIT_CURRENT_HASH) + + build_source_link_rule(${REPO_ROOT} ${GIT_REMOTE} ${GIT_CURRENT_HASH} ROOT_RULE) + + set(SOURCE_LINK_RULES) + list(APPEND SOURCE_LINK_RULES ${ROOT_RULE}) + + # Also build rules for submodules + run_git_submodule_foreach("echo $displaypath,$sha1,`git config --get remote.origin.url`" ${REPO_ROOT} SUBMODULE_INFO) + + if (NOT ("${SUBMODULE_INFO}" STREQUAL "")) + # Turn output of new lines into a CMake list + string(REPLACE "\r\n" ";" SUBMODULE_INFO ${SUBMODULE_INFO}) + string(REPLACE "\n" ";" SUBMODULE_INFO ${SUBMODULE_INFO}) + + foreach(ITEM ${SUBMODULE_INFO}) + # Turn each line into a list of path;hash;url + string(REPLACE "," ";" SUBMODULE ${ITEM}) + list(GET SUBMODULE 0 LOCAL_PATH) + list(GET SUBMODULE 1 CURRENT_HASH) + list(GET SUBMODULE 2 REMOTE) + string(CONCAT LOCAL_PATH "${REPO_ROOT}/" ${LOCAL_PATH}) + build_source_link_rule(${LOCAL_PATH} ${REMOTE} ${CURRENT_HASH} RULE) + list(APPEND SOURCE_LINK_RULES ${RULE}) + endforeach() + endif() + + set(OUTPUT) + string(APPEND OUTPUT "{\n") + string(APPEND OUTPUT "\"documents\": {\n") + string(JOIN ",\n" EXPANDED_RULES ${SOURCE_LINK_RULES}) + string(APPEND OUTPUT "${EXPANDED_RULES}\n") + string(APPEND OUTPUT "}\n") + string(APPEND OUTPUT "}\n") + + configure_file(${SOURCE_LINK_JSON_INPUT_PATH} ${SOURCE_LINK_JSON_PATH}) + +endfunction() + +function(build_source_link_rule LOCAL_PATH GIT_REMOTE GIT_CURRENT_HASH OUTPUT) + # Verify local path exists + if (NOT (IS_DIRECTORY ${LOCAL_PATH})) + message(FATAL_ERROR "${LOCAL_PATH} is not a directory") + endif() + + # Change local path to native path + file(TO_NATIVE_PATH "${LOCAL_PATH}/*" LOCAL_PATH) + # Escape any backslashes for JSON + string(REPLACE "\\" "\\\\" LOCAL_PATH ${LOCAL_PATH}) + + # Verify this is a GitHub URL + # In the future we could support other source servers but currently they + # are not supported + if (NOT ("${GIT_REMOTE}" MATCHES "https://github\\.com")) + message(STATUS "Unable to sourcelink remote: \"${GIT_REMOTE}\". Unknown host") + return() + endif() + + string(REPLACE ".git" "" RAW_GIT_URL ${GIT_REMOTE}) + string(REPLACE "github.com" "raw.githubusercontent.com" RAW_GIT_URL ${RAW_GIT_URL}) + string(CONCAT RAW_GIT_URL ${RAW_GIT_URL} "/${GIT_CURRENT_HASH}/*") + + set(${OUTPUT} "\"${LOCAL_PATH}\" : \"${RAW_GIT_URL}\"" PARENT_SCOPE) + +endfunction(build_source_link_rule) diff --git a/cmake/source_link.json.in b/cmake/source_link.json.in new file mode 100644 index 0000000..e5f1b4d --- /dev/null +++ b/cmake/source_link.json.in @@ -0,0 +1 @@ +@OUTPUT@ diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt new file mode 100644 index 0000000..decd140 --- /dev/null +++ b/common/CMakeLists.txt @@ -0,0 +1,13 @@ +project(Common) + +set (SOURCES + version.cpp +) + +add_library(Common STATIC ${SOURCES}) +target_compile_features(Common PUBLIC cxx_std_20) +wpilib_target_warnings(Common) +target_include_directories(Common PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + +target_compile_definitions(Common PRIVATE MRC_BUILD_GIT_HASH=${BUILD_CURRENT_GIT_HASH}) +target_compile_definitions(Common PRIVATE MRC_BUILD_TIMESTAMP=${BUILD_CURRENT_TIMESTAMP}) diff --git a/common/version.cpp b/common/version.cpp new file mode 100644 index 0000000..e955443 --- /dev/null +++ b/common/version.cpp @@ -0,0 +1,30 @@ +#include "version.h" + +#define STR_HELPER(x) #x +#define STR_VER(x) STR_HELPER(x) + +// Leave these as globals. We want them accessable in the debugger. +const char* MRC_GitHash = +#ifndef MRC_BUILD_GIT_HASH + "Unknown"; +#else + STR_VER(MRC_BUILD_GIT_HASH); +#endif + +const char* MRC_BuildTimestamp = +#ifndef MRC_BUILD_TIMESTAMP + "Unknown"; +#else + STR_VER(MRC_BUILD_TIMESTAMP); +#endif + +extern "C" { + +const char* MRC_GetGitHash(void) { + return MRC_GitHash; +} + +const char* MRC_GetBuildTimestamp(void) { + return MRC_BuildTimestamp; +} +} diff --git a/common/version.h b/common/version.h new file mode 100644 index 0000000..2760c07 --- /dev/null +++ b/common/version.h @@ -0,0 +1,12 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +const char* MRC_GetGitHash(void); +const char* MRC_GetBuildTimestamp(void); + +#ifdef __cplusplus +} +#endif diff --git a/radio/CMakeLists.txt b/radio/CMakeLists.txt new file mode 100644 index 0000000..867ff8c --- /dev/null +++ b/radio/CMakeLists.txt @@ -0,0 +1,10 @@ +project(RadioDaemon) + +add_executable(RadioDaemon main.cpp) +target_compile_features(RadioDaemon PRIVATE cxx_std_20) +wpilib_target_warnings(RadioDaemon) +target_link_libraries(RadioDaemon PRIVATE Common ntcore wpinet wpiutil) + +if (MRC_BUILD) +target_compile_definitions(RadioDaemon PRIVATE MRC_DAEMON_BUILD) +endif() diff --git a/radio/main.cpp b/radio/main.cpp new file mode 100644 index 0000000..1b27df4 --- /dev/null +++ b/radio/main.cpp @@ -0,0 +1,145 @@ +#if defined(__linux__) && defined(MRC_DAEMON_BUILD) +#include +#endif +#include + +#include "version.h" + +#include +#include +#include +#include "wpinet/HttpUtil.h" + +#include "networktables/NetworkTableInstance.h" +#include "networktables/StringTopic.h" +#include "wpi/StringExtras.h" + +struct DataStorage { + nt::StringSubscriber teamSubscriber; + + std::shared_ptr tcpConn; + std::optional oldTeamNumber; +}; + +static bool startUvLoop(wpi::uv::Loop& loop, DataStorage& instData); + +int main() { + printf("Starting RadioDaemon\n"); + printf("\tBuild Hash: %s\n", MRC_GetGitHash()); + printf("\tBuild Timestamp: %s\n", MRC_GetBuildTimestamp()); + +#if defined(__linux__) && defined(MRC_DAEMON_BUILD) + sigset_t signal_set; + sigemptyset(&signal_set); + sigaddset(&signal_set, SIGTERM); + sigaddset(&signal_set, SIGINT); + sigprocmask(SIG_BLOCK, &signal_set, nullptr); +#endif + + auto ntInst = nt::NetworkTableInstance::Create(); + ntInst.SetServer({"localhost"}, 6810); + ntInst.StartClient4("RadioDaemon"); + + DataStorage instData; + + instData.teamSubscriber = ntInst.GetStringTopic("/sys/team").Subscribe(""); + + wpi::EventLoopRunner loopRunner; + + bool success = false; + loopRunner.ExecSync([&success, &instData](wpi::uv::Loop& loop) { + success = startUvLoop(loop, instData); + }); + + if (!success) { + loopRunner.Stop(); + return -1; + } + + { +#if defined(__linux__) && defined(MRC_DAEMON_BUILD) + int sig = 0; + sigwait(&signal_set, &sig); +#else + (void)getchar(); +#endif + } + loopRunner.Stop(); + ntInst.StopClient(); + nt::NetworkTableInstance::Destroy(ntInst); + + return 0; +} + +static std::optional checkTeamNumber(DataStorage& instData) { + auto teamStr = instData.teamSubscriber.Get(); + auto maybeTeam = wpi::parse_integer(teamStr, 10); + if (!maybeTeam.has_value()) { + printf("failed to parse team number\n"); + return {}; + } + + auto team = *maybeTeam; + + if (team > 25599) { + printf("Team number too large\n"); + return {}; + } + + return team; +} + +static void handleNewConnection(wpi::uv::Loop& loop, DataStorage& instData) { + auto tcp = wpi::uv::Tcp::Create(loop, 0); + + tcp->error.connect +} + +static void handleTeamUpdate(wpi::uv::Loop& loop, DataStorage& instData) {} + +static bool startUvLoop(wpi::uv::Loop& loop, DataStorage& instData) { + auto timer = wpi::uv::Timer::Create(loop); + if (!timer) { + return false; + } + + timer->timeout.connect([&loop, &instData] { + auto maybeTeam = checkTeamNumber(instData); + if (!maybeTeam.has_value()) { + instData.oldTeamNumber = maybeTeam; + return; + } + + if (maybeTeam != instData.oldTeamNumber) { + // New team number. + instData.oldTeamNumber = maybeTeam; + // Restart TCP if it exists. + + if (instData.tcpConn) { + instData.tcpConn->Shutdown(); + instData.tcpConn->Close(); + instData.tcpConn = nullptr; + } + + handleNewConnection(loop, instData); + return; + } + + handleTeamUpdate(loop, instData); + + // fmt::format("10.{}.{}.1", static_cast(team / 100), + // static_cast(team % 100)); + + // auto tcp = wpi::uv::Tcp::Create(loop, 0); + // if (!tcp) { + // printf("Failed to allocate tcp\n"); + // return; + // } + + // tcp->Reuse + }); + + timer->Start(wpi::uv::Timer::Time{100}, wpi::uv::Timer::Time{3000}); + + return true; +}