diff --git a/deploy.py b/deploy.py index 18b91be..7dbb383 100755 --- a/deploy.py +++ b/deploy.py @@ -2,17 +2,16 @@ """Deploy executable and libraries to the roborio. -After compiling the robot package, this script finds the roborio address, copies over the executable -and wpilib libraries, and runs several scripts remotely. +Referenced off of gradlerio (download an example C++ project and run gradle deploy to see the logs) positional arguments: package robot package to deploy optional arguments: -h, --help show this help message and exit - --verbose, -v log shell commands - --skip-wpilib, -s skip deploying wpilib libraries - --build, -b run cargo build automatically before deploying + --skip-libs, -s skip deploying shared libraries + --build, -b run cargo build before deploying + --debug, -d don't add --release flag to cargo build e.g. `./deploy.py rswerve -sb` build and deploys the rswerve package, skipping wpilib libraries """ @@ -22,11 +21,12 @@ import sys from os import path -EXECUTABLE_DIR = "target/arm-unknown-linux-gnueabi/release" +EXECUTABLE_DIR = "target/arm-unknown-linux-gnueabi" SHARED_LIB_DIR = "frc-sys/lib/linux/athena/shared" WPILIB_LIBS = [ "cameraserver", + "cscore", "ntcore", "wpiHal", "wpilibc", @@ -34,6 +34,25 @@ "wpiutil", ] +THIRD_PARTY_LIBS = [ + "opencv_stitching", + "opencv_videoio", + "opencv_flann", + "opencv_video", + "opencv_imgcodecs", + "opencv_highgui", + "opencv_objdetect", + "opencv_imgproc", + "opencv_calib3d", + "opencv_core", + "opencv_features2d", + "opencv_photo", + "opencv_ml", + "opencv_shape", + "opencv_superres", + "opencv_videostab", +] + ROBORIO_HOME_DIR = "/home/lvuser/" ROBORIO_LIB_DIR = "/usr/local/frc/third-party/lib" @@ -48,44 +67,68 @@ def main(): parser = ArgumentParser(description="Deploy code to the roborio.") parser.add_argument("package", type=str, help="robot package to deploy") - parser.add_argument("--verbose", "-v", action="store_true", help="log shell commands") - parser.add_argument("--skip-wpilib", "-s", action="store_true", - help="skip deploying wpilib libraries") + parser.add_argument("--skip-libs", "-s", action="store_true", + help="skip deploying shared libraries") parser.add_argument("--build", "-b", action="store_true", - help="run cargo build automatically before deploying") + help="run cargo build before deploying") + parser.add_argument("--debug", "-d", action="store_true", + help="don't add --release flag to cargo build") args = parser.parse_args() + ##### + + # run cargo build, unless -b is passed if args.build: - build_package() + cmd(f"cargo build -p {args.package} {'' if args.debug else '--release'}", + suppress=False) - verify_executable() - address = find_roborio() + print(f"\n\nDeploying {args.package}") + print("(C: running command on robot, F: copying file to robot)\n\n") - if not args.skip_wpilib: - deploy_wpilib_libs() + # path to executable + exe = path.join(EXECUTABLE_DIR, "debug" if args.debug else "release", args.package) - run_predeploy() - deploy_executable() - run_postdeploy() + # verify executable exists + if not path.exists(exe): + sys.exit(f"Robot executable {exe} not found.") + # find roborio address + address = find_roborio() -def build_package(): - """Run cargo build --release on the robot package.""" + cmd_ssh("sed -i -e 's/^StartupDLLs/;StartupDLLs/' /etc/natinst/share/ni-rt.ini") - print(f"Building {args.package}") - cmd(f"cargo build -p {args.package} --release", suppress=False) + # deploy shared libraries, unless -s is passed + if not args.skip_libs: + for lib in WPILIB_LIBS: + if args.debug: + f = path.join(SHARED_LIB_DIR, f"lib{lib}d.so") + else: + f = path.join(SHARED_LIB_DIR, f"lib{lib}.so") + scp(f, ROBORIO_LIB_DIR) + for lib in THIRD_PARTY_LIBS: + scp(path.join(SHARED_LIB_DIR, f"lib{lib}.so"), ROBORIO_LIB_DIR) -def verify_executable(): - """Verify that the robot executable exists. + cmd_ssh("chmod -R 777 \"/usr/local/frc/third-party/lib\" || true; chown -R lvuser:ni \"/usr/local/frc/third-party/lib\"") + cmd_ssh("ldconfig") - The robot package must be built in the release profile. - """ + # kill robot and delete previous executable on robot + cmd_ssh(". /etc/profile.d/natinst-path.sh; /usr/local/frc/bin/frcKillRobot.sh -t 2> /dev/null") + cmd_ssh("rm -f \"/home/lvuser/frcUserProgram\"") - exe = path.join(EXECUTABLE_DIR, args.package) - if not path.exists(exe): - sys.exit(f"Robot executable {exe} not found.") - print(f"Executable found at {exe}") + # copy new executable to robot (named frcUserProgram) + scp(exe, path.join(ROBORIO_HOME_DIR, "frcUserProgram")) + + cmd_ssh("echo ' \"/home/lvuser/frcUserProgram\" ' > /home/lvuser/robotCommand") + cmd_ssh("chmod +x /home/lvuser/robotCommand; chown lvuser /home/lvuser/robotCommand") + cmd_ssh("chmod +x /home/lvuser/frcUserProgram; chown lvuser /home/lvuser/frcUserProgram") + cmd_ssh("chmod +x \"/home/lvuser/frcUserProgram\"; chown lvuser \"/home/lvuser/frcUserProgram\"") + cmd_ssh("setcap cap_sys_nice+eip \"/home/lvuser/frcUserProgram\"") + cmd_ssh("sync") + cmd_ssh("ldconfig") + cmd_ssh(". /etc/profile.d/natinst-path.sh; /usr/local/frc/bin/frcKillRobot.sh -t -r 2> /dev/null") + + print("\n\nDone") def find_roborio(): @@ -102,8 +145,7 @@ def find_roborio(): for address in addresses: print(f"Pinging {address}... ", end="", flush=True) - ping = cmd(f"ping -c 1 {address}") - if ping.returncode == 0: + if cmd(f"ping -c 1 {address}"): print("Success") return address else: @@ -112,84 +154,33 @@ def find_roborio(): sys.exit("Unable to connect to roborio.") -def deploy_wpilib_libs(): - """Copies wpilib libraries to the roborio.""" - - for lib in WPILIB_LIBS: - f = path.join(SHARED_LIB_DIR, f"lib{lib}.so") - print(f"Deploying vendor library: {f}") - scp(f, ROBORIO_LIB_DIR) - - -def run_predeploy(): - """Run predeploy scripts on the roborio. - - Based on https://github.com/wpilibsuite/GradleRIO/blob/HEAD/src/main/groovy/edu/wpi/first/gradlerio/frc/FRCNativeArtifact.groovy - """ - - print("Running predeploy scripts") - commands = [ - ". /etc/profile.d/natinst-path.sh; /usr/local/frc/bin/frcKillRobot.sh -t 2> /dev/null", - f"rm -f {path.join(ROBORIO_HOME_DIR, args.package)}" - ] - - for c in commands: - cmd_ssh(c) - - -def deploy_executable(): - """Copies the robot executable to the roborio.""" - - f = path.join(EXECUTABLE_DIR, args.package) - print(f"Deploying executable: {f}") - cmd_ssh([f"rm -f /home/lvuser/{args.package}"]) - scp(f, ROBORIO_HOME_DIR) - - -def run_postdeploy(): - """Runs postdeploy scripts on the roborio. - - Based on https://github.com/wpilibsuite/GradleRIO/blob/HEAD/src/main/groovy/edu/wpi/first/gradlerio/frc/FRCNativeArtifact.groovy - - Also updates the robotCommand file in the roborio. - """ - - print("Running postdeploy scripts") - file = path.join(ROBORIO_HOME_DIR, args.package) - commands = [ - f"echo \"{file}\" > /home/lvuser/robotCommand", - "chmod +x /home/lvuser/robotCommand; chown lvuser /home/lvuser/robotCommand", - f"chmod +x {file}; chown lvuser {file}", - "sync", - "ldconfig", - ". /etc/profile.d/natinst-path.sh; /usr/local/frc/bin/frcKillRobot.sh -t -r 2> /dev/null" - ] - - for c in commands: - cmd_ssh(c) - - def cmd_ssh(command): """Run a command on the roborio.""" - cmd(f"ssh admin@{address} '{command}'") + print(f"C: {command}") + cmd(f"ssh admin@{address} \"{command}\"") def scp(source, target): """Copy files from local to the roborio.""" + print(f"F: {source} -> {target}") cmd(f"scp {source} admin@{address}:{target}") def cmd(command, suppress=True): - """Run a command (suppresses output).""" + """Run a command.""" - if args.verbose: - print(command) if suppress: - return subprocess.run(command, shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + result = subprocess.run(command, shell=True, + stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) else: - return subprocess.run(command, shell=True) + result = subprocess.run(command, shell=True) + + if result.returncode != 0: + sys.exit(f"Previous command exited with code {result.returncode}") + + return result if __name__ == "__main__": diff --git a/frc-sys/build.rs b/frc-sys/build.rs index 040fcad..e58c4a4 100644 --- a/frc-sys/build.rs +++ b/frc-sys/build.rs @@ -20,12 +20,17 @@ const STATIC_LIBS: &[&str] = &[ "SparkMaxDriver", ]; -const SHARED_LIBS: &[&str] = &[ +const WPILIB_LIBS: &[&str] = &[ "wpilibc", "ntcore", "wpiHal", "wpiutil", "wpimath", + "cameraserver", + "cscore", +]; + +const SHARED_LIBS: &[&str] = &[ "RoboRIO_FRC_ChipObject", "FRC_NetworkCommunication", "visa", @@ -37,7 +42,22 @@ const SHARED_LIBS: &[&str] = &[ "niriodevenum", "NiFpgaLv", "niriosession", - "cameraserver", + "opencv_stitching", + "opencv_videoio", + "opencv_flann", + "opencv_video", + "opencv_imgcodecs", + "opencv_highgui", + "opencv_objdetect", + "opencv_imgproc", + "opencv_calib3d", + "opencv_core", + "opencv_features2d", + "opencv_photo", + "opencv_ml", + "opencv_shape", + "opencv_superres", + "opencv_videostab", ]; // Path to libraries @@ -101,26 +121,34 @@ fn main() { .blocklist_function("wpi::.*"), ); + // Search for libraries + for i in &["static", "shared"] { + println!( + "cargo:rustc-link-search={}", + CWD.join(LIB_DIR).join(i).to_str().unwrap(), + ); + } + // Link with static libraries - println!( - "cargo:rustc-link-search={}", - CWD.join(LIB_DIR).join("static").to_str().unwrap(), - ); for lib in STATIC_LIBS { println!("cargo:rustc-link-lib={}", lib); } - // Link with shared libraries - println!( - "cargo:rustc-link-search={}", - CWD.join(LIB_DIR).join("shared").to_str().unwrap(), - ); + // Link with wpilib libraries + let is_debug = env::var("PROFILE").unwrap() == "debug"; + for lib in WPILIB_LIBS { + // Debug versions of wpilib libraries are postfixed with "d" + if is_debug { + println!("cargo:rustc-link-lib={}d", lib) + } else { + println!("cargo:rustc-link-lib={}", lib) + } + } + + // Link with other shared libraries for lib in SHARED_LIBS { println!("cargo:rustc-link-lib={}", lib) } - - // Link with stdc++ - println!("cargo:rustc-link-lib=stdc++"); } fn compile_cc(name: &str) { diff --git a/frc-sys/install_deps.py b/frc-sys/install_deps.py index 11608da..6790b43 100755 --- a/frc-sys/install_deps.py +++ b/frc-sys/install_deps.py @@ -4,6 +4,8 @@ Compiled libraries and headers are downloaded from each vendor's maven repository linked in vendordeps JSON files. + +See vendordeps/README.md """ from datetime import datetime @@ -16,19 +18,17 @@ import sys from urllib import request -VENDORDEPS_DIR = "vendordeps" +VENDOR_DEPS_DIR = "vendordeps" +WPILIB_DEPS_DIR = "vendordeps/wpilib" + INCLUDE_DIR = "include" LIB_DIR = "lib" -WPILIB_DEP = "wpilib" -ROBORIO_DEP = "roborio" -VENDOR_DEPS = ["navx_frc", "Phoenix-latest", "REVColorSensorV3", "REVRobotics"] - def main(): # Clean include and lib directories before downloading new deps for dir in [INCLUDE_DIR, LIB_DIR]: - # Delete all contents except hidden files (doesn't delete .gitkeep) + # Delete all contents except hidden files (except .gitkeep) for f in os.listdir(dir): if f == ".gitkeep": continue @@ -39,47 +39,34 @@ def main(): else: shutil.rmtree(p) - # Download all wpilib deps - with open(path.join(VENDORDEPS_DIR, f"{WPILIB_DEP}.json"), 'r') as wpilib_dep_file: - j = json.load(wpilib_dep_file) - for dep in j['dependencies']: - install_dep( - 'wpilib', - j['mavenUrl'], - f"edu.wpi.first.{dep}", - f"{dep}-cpp", - j['version'], - static=False, - headers=True, - ) - - # Download all roborio runtime dpes - with open(path.join(VENDORDEPS_DIR, f"{ROBORIO_DEP}.json"), 'r') as roborio_dep_file: - j = json.load(roborio_dep_file) - for dep in j['dependencies']: - install_dep( - dep['name'], - j['mavenUrl'], - "", - dep['name'], - dep['version'], - static=False, - headers=False, - ) - - # Download all vendor deps - for vendor_dep in VENDOR_DEPS: - with open(path.join(VENDORDEPS_DIR, f"{vendor_dep}.json"), 'r') as vendordep_file: - j = json.load(vendordep_file) + # Download wpilib related deps + for vendordep in glob(path.join(WPILIB_DEPS_DIR, "*.json")): + with open(vendordep, 'r') as f: + j = json.load(f) + for dep in j['dependencies']: + # Files usually located in //-, except for ni-libraries + group_id = dep if j['name'] != "ni-libraries" else "" + artifact_id = f"{dep}-cpp" if j['name'] != "ni-libraries" else dep + + install_dep( + j['mavenUrl'], + group_id, + artifact_id, + j['version'], + static=False, + headers=j['headers'], + ) + # Download vendor dependencies + for vendordep in glob(path.join(VENDOR_DEPS_DIR, "*.json")): + with open(vendordep, 'r') as f: + j = json.load(f) for dep in j['cppDependencies']: # Skip dependencies that arent compiled for roborio (probably simulation stuff) if "linuxathena" not in dep['binaryPlatforms']: - # print(f"Skipping {dep['groupId']}-{dep['artifactId']}") continue install_dep( - j['name'], j['mavenUrls'][0], dep['groupId'], dep['artifactId'], @@ -90,9 +77,12 @@ def main(): full_lib_dir = path.join(LIB_DIR, "linux", "athena", "shared") - # Delete debug versions (e.g. libwpilibc.so.debug) - for f in glob(path.join(full_lib_dir, f"*.debug")): - os.remove(f) + # Rename debug versions (e.g. libwpilibc.so.debug -> libwpilibcd.so) + r = re.compile('(.*)\.so\.debug') + for f in os.listdir(full_lib_dir): + m = r.match(f) + if m: + os.rename(path.join(full_lib_dir, f), path.join(full_lib_dir, f"{m.group(1)}d.so")) # Create symlinks to shared libraries with version numbers (e.g. libNiFpga.so -> libNiFpga.so.19.0.0) r = re.compile('(.*\.so)\..*') @@ -103,7 +93,7 @@ def main(): # Delete misc files (notices, licenses, etc.) for dir in [INCLUDE_DIR, LIB_DIR]: - for g in ["LICENSE.*", "RequiredVersion.txt", "ThirdPartyNotices.txt"]: + for g in ["LICENSE*", "RequiredVersion.txt", "ThirdPartyNotices.txt"]: for f in glob(path.join(dir, g)): os.remove(f) @@ -113,7 +103,7 @@ def main(): f.write(f"Files downloaded by ../install_deps.py on {datetime.now()}") -def install_dep(vendor_name, maven_url, group_id, artifact_id, version, static, headers): +def install_dep(maven_url, group_id, artifact_id, version, static, headers): """Download a dependency headers and static library.""" url = urljoin(maven_url, group_id.replace('.', '/'), artifact_id, version) @@ -144,7 +134,9 @@ def download_and_unzip(url, dir, name): def urljoin(*components): - """Join url pieces together.""" + """Join url path components together.""" + + components = filter(lambda c: c, components) return '/'.join([c.strip('/') for c in components]) diff --git a/frc-sys/vendordeps/roborio.json b/frc-sys/vendordeps/roborio.json deleted file mode 100644 index 44b06b4..0000000 --- a/frc-sys/vendordeps/roborio.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "mavenUrl": "https://frcmaven.wpi.edu/artifactory/release/edu/wpi/first/ni-libraries/", - "dependencies": [ - { - "name": "chipobject", - "version": "2020.9.2" - }, - { - "name": "netcomm", - "version": "2020.9.2" - }, - { - "name": "runtime", - "version": "2020.10.1" - }, - { - "name": "visa", - "version": "2020.10.1" - } - ] -} \ No newline at end of file diff --git a/frc-sys/vendordeps/wpilib/ni-libraries.json b/frc-sys/vendordeps/wpilib/ni-libraries.json new file mode 100644 index 0000000..8430ff2 --- /dev/null +++ b/frc-sys/vendordeps/wpilib/ni-libraries.json @@ -0,0 +1,12 @@ +{ + "name": "ni-libraries", + "mavenUrl": "https://frcmaven.wpi.edu/artifactory/release/edu/wpi/first/ni-libraries/", + "headers": false, + "version": "2020.10.1", + "dependencies": [ + "chipobject", + "netcomm", + "runtime", + "visa" + ] +} \ No newline at end of file diff --git a/frc-sys/vendordeps/wpilib/opencv.json b/frc-sys/vendordeps/wpilib/opencv.json new file mode 100644 index 0000000..512a220 --- /dev/null +++ b/frc-sys/vendordeps/wpilib/opencv.json @@ -0,0 +1,9 @@ +{ + "name": "opencv", + "mavenUrl": "https://frcmaven.wpi.edu/artifactory/release/edu/wpi/first/thirdparty/frc2021", + "headers": false, + "version": "3.4.7-5", + "dependencies": [ + "opencv" + ] +} \ No newline at end of file diff --git a/frc-sys/vendordeps/wpilib.json b/frc-sys/vendordeps/wpilib/wpilib.json similarity index 55% rename from frc-sys/vendordeps/wpilib.json rename to frc-sys/vendordeps/wpilib/wpilib.json index c3f2652..b1a121c 100644 --- a/frc-sys/vendordeps/wpilib.json +++ b/frc-sys/vendordeps/wpilib/wpilib.json @@ -1,8 +1,11 @@ { - "mavenUrl": "https://frcmaven.wpi.edu/artifactory/release/", + "name": "wpilib", + "mavenUrl": "https://frcmaven.wpi.edu/artifactory/release/edu/wpi/first", + "headers": true, "version": "2021.3.1", "dependencies": [ "cameraserver", + "cscore", "hal", "ntcore", "wpilibc",