diff --git a/flake.nix b/flake.nix index 7704bc9e..96f07e86 100644 --- a/flake.nix +++ b/flake.nix @@ -101,6 +101,7 @@ in pkgs.pkgsStatic.localPackages.hpcstat.override { inherit openssh duc; standalone = true; version = inputs.self.rev or "dirty"; }; ufo = pkgs.pkgsStatic.localPackages.ufo.override { version = inputs.self.rev or "dirty"; }; + chn-bsub = pkgs.pkgsStatic.localPackages.chn-bsub; nixpkgs = pkgs; } // ( @@ -207,6 +208,12 @@ packages = [ pkgs.clang-tools_18 ]; CMAKE_EXPORT_COMPILE_COMMANDS = "1"; }; + chn-bsub = pkgs.mkShell + { + inputsFrom = with pkgs.localPackages; [ chn-bsub ]; + buildInputs = [ pkgs.clang-tools_18 ]; + CMAKE_EXPORT_COMPILE_COMMANDS = "1"; + }; }; }; } diff --git a/local/pkgs/chn-bsub/CMakeLists.txt b/local/pkgs/chn-bsub/CMakeLists.txt new file mode 100644 index 00000000..c5c37c7f --- /dev/null +++ b/local/pkgs/chn-bsub/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.14) +project(chn-bsub VERSION 0.0.0 LANGUAGES CXX) +enable_testing() +include(GNUInstallDirs) + +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + message("Setting build type to 'Release' as none was specified.") + set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build." FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo") +endif() + +find_package(ftxui REQUIRED) +find_package(Boost REQUIRED COMPONENTS filesystem iostreams) +find_package(range-v3 REQUIRED) +find_package(biu REQUIRED) + +add_executable(chn-bsub src/main.cpp) +target_compile_features(chn-bsub PUBLIC cxx_std_23) +target_link_libraries(chn-bsub PRIVATE fmt::fmt ftxui::screen ftxui::dom ftxui::component Boost::filesystem + range-v3::range-v3 biu::biu) + +install(TARGETS chn-bsub RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + +get_property(ImportedTargets DIRECTORY "${CMAKE_SOURCE_DIR}" PROPERTY IMPORTED_TARGETS) +message("Imported targets: ${ImportedTargets}") +message("List of compile features: ${CMAKE_CXX_COMPILE_FEATURES}") diff --git a/local/pkgs/chn-bsub/default.nix b/local/pkgs/chn-bsub/default.nix new file mode 100644 index 00000000..933f2fd2 --- /dev/null +++ b/local/pkgs/chn-bsub/default.nix @@ -0,0 +1,12 @@ +{ + stdenv, lib, sbatchConfig ? null, substituteAll, runCommand, + cmake, pkg-config, ftxui, biu +}: +stdenv.mkDerivation +{ + name = "chn-bsub"; + src = ./.; + buildInputs = [ ftxui biu ]; + nativeBuildInputs = [ cmake pkg-config ]; + postInstall = "ln -s chn-bsub $out/bin/chn_bsub"; +} diff --git a/local/pkgs/chn-bsub/src/main.cpp b/local/pkgs/chn-bsub/src/main.cpp new file mode 100644 index 00000000..3b9f00ff --- /dev/null +++ b/local/pkgs/chn-bsub/src/main.cpp @@ -0,0 +1,202 @@ +# include +# include +# include +# include +# include +# include +# include +# include + +using namespace biu::literals; + +int main() +{ + // 需要绑定到界面上的变量 + struct + { + std::array vasp_version_selected = {0, 0, 0}; + std::vector vasp_version_entries_level1 = {"640", "631"}; + std::map> vasp_version_entries_level2 = + { + {"640", {"(default)", "fixc", "optcell_vtst_wannier90", "shmem", "vtst"}}, + {"631", {"shmem"}} + }; + std::vector vasp_version_entries_level3 = {"std", "gam", "ncl"}; + + int queue_selected = 0; + std::vector queue_entries = + { + "normal_1day", "normal_1week", "normal", + "normal_1day_new", "ocean_530_1day", "ocean6226R_1day" + }; + std::map max_cores = + { + {"normal_1day", 28}, {"normal_1week", 28}, {"normal", 20}, + {"normal_1day_new", 24}, {"ocean_530_1day", 24}, {"ocean6226R_1day", 32} + }; + std::string ncores = ""; + std::string job_name = [] + { + // /data/gpfs01/jykang/linwei/chn/lammps-SiC + std::vector paths; + boost::split(paths, std::filesystem::current_path().string(), + boost::is_any_of("/")); + if (paths.size() < 7) + return "my-great-job"s; + else + return paths[5] + "_" + paths.back(); + }(); + std::string bsub = ""; + std::string user_command = ""; + } state; + + // 为组件增加标题栏 + auto component_with_title = [](std::string title, ftxui::Component component) + { + return ftxui::Renderer(component, [title, component] + { + return ftxui::vbox + ({ + ftxui::text(title) | ftxui::bgcolor(ftxui::Color::Blue), + component->Render(), + ftxui::separator() + }); + }); + }; + + // 构建界面, 需要至少 25 行 47 列 + auto screen = ftxui::ScreenInteractive::Fullscreen(); + auto request_interface = [&state, &screen, &component_with_title] + { + auto vasp_version_level1 = ftxui::Menu + (&state.vasp_version_entries_level1, &state.vasp_version_selected[0]) + | ftxui::size(ftxui::WIDTH, ftxui::EQUAL, 8); + std::vector vasp_version_level2_children; + for (auto& i : state.vasp_version_entries_level1) + vasp_version_level2_children.push_back(ftxui::Menu + ( + &state.vasp_version_entries_level2[i], + &state.vasp_version_selected[1] + )); + auto vasp_version_level2 = ftxui::Container::Tab + ( + vasp_version_level2_children, + &state.vasp_version_selected[0] + ) | ftxui::size(ftxui::WIDTH, ftxui::EQUAL, 27); + auto vasp_version_level3 = ftxui::Menu + (&state.vasp_version_entries_level3, &state.vasp_version_selected[2]) + | ftxui::size(ftxui::WIDTH, ftxui::EQUAL, 8); + auto vasp_version = component_with_title("Select vasp version:", + ftxui::Container::Horizontal + ({vasp_version_level1, vasp_version_level2, vasp_version_level3}) + | ftxui::size(ftxui::HEIGHT, ftxui::EQUAL, 5)); + auto queue = component_with_title("Select queue:", + ftxui::Menu(&state.queue_entries, &state.queue_selected) + | ftxui::size(ftxui::HEIGHT, ftxui::EQUAL, 6)); + auto ncores = component_with_title("Input cores you want to use:", + ftxui::Input(&state.ncores, "(leave blank to use all cores)")) + | ftxui::size(ftxui::HEIGHT, ftxui::EQUAL, 3); + auto job_name = component_with_title("Job name:", + ftxui::Input(&state.job_name, "")) + | ftxui::size(ftxui::HEIGHT, ftxui::EQUAL, 3); + auto continue_button = ftxui::Button("Continue", + [&]{state.user_command = "continue"; screen.ExitLoopClosure()();}); + auto quit_button = ftxui::Button("Quit", + [&]{state.user_command = "quit"; screen.ExitLoopClosure()();}); + return ftxui::Container::Vertical + ({ + vasp_version, queue, ncores, job_name, + ftxui::Container::Horizontal({continue_button, quit_button}) + }) | ftxui::borderHeavy + | ftxui::size(ftxui::WIDTH, ftxui::EQUAL, 47) + | ftxui::size(ftxui::HEIGHT, ftxui::EQUAL, 24); + }(); + auto confirm_interface = [&state, &screen, &component_with_title] + { + ftxui::InputOption input_option; + input_option.multiline = true; + return ftxui::Container::Vertical + ({ + component_with_title + ( + "Double check & modify submit command:", + ftxui::Input(&state.bsub, "", input_option) + ) + | ftxui::size(ftxui::HEIGHT, ftxui::EQUAL, 7), + ftxui::Container::Horizontal + ({ + ftxui::Button("Submit", + [&]{state.user_command = "submit"; screen.ExitLoopClosure()();}), + ftxui::Button("Quit", + [&]{state.user_command = "quit"; screen.ExitLoopClosure()();}), + ftxui::Button("Back", + [&]{state.user_command = "back"; screen.ExitLoopClosure()();}) + }), + ftxui::Renderer([]{return ftxui::vbox + ({ + ftxui::separator(), + ftxui::text("Source code:"), + ftxui::text("https://github.com/CHN-beta/chn_bsub.git"), + ftxui::text("Star & PR are welcome!"), + });}) + }) | ftxui::borderHeavy + | ftxui::size(ftxui::WIDTH, ftxui::EQUAL, 47) + | ftxui::size(ftxui::HEIGHT, ftxui::EQUAL, 14); + }(); + + // 实际投递任务 + auto submit = [](std::string bsub) + { + // replace \n with space + boost::replace_all(bsub, "\n", " "); + auto process = boost::process::child + ( + boost::process::search_path("sh"), "-c", bsub, + boost::process::std_in.close(), + boost::process::std_out > stdout, + boost::process::std_err > stderr + ); + process.wait(); + }; + + // 进入事件循环 + while (true) + { + screen.Loop(request_interface); + if (state.user_command == "quit") + return EXIT_FAILURE; + else if (state.user_command != "continue") + throw std::runtime_error("user_command is not recognized"); + state.bsub = fmt::format + ( + "bsub -J '{}'\n-q {}\n-n {}\n-R 'span[hosts=1]'\n-o 'output.txt'\nchn_vasp.sh {}", + state.job_name, + state.queue_entries[state.queue_selected], + state.ncores.empty() ? state.max_cores[state.queue_entries[state.queue_selected]] : + std::stoi(state.ncores), + [&] + { + auto version_level1 = state.vasp_version_entries_level1[state.vasp_version_selected[0]]; + auto version_level2 = state.vasp_version_entries_level2[version_level1] + [state.vasp_version_selected[1]]; + auto version_level3 = state.vasp_version_entries_level3[state.vasp_version_selected[2]]; + return fmt::format + ( + "{}{}_{}", + version_level1, + version_level2 == "(default)" ? ""s : "_" + version_level2, + version_level3 + ); + }() + ); + screen.Loop(confirm_interface); + if (state.user_command == "quit") + return EXIT_FAILURE; + else if (state.user_command == "back") + continue; + else if (state.user_command != "submit") + throw std::runtime_error("user_command is not recognized"); + submit(state.bsub); + break; + } +} diff --git a/local/pkgs/default.nix b/local/pkgs/default.nix index 4e8ee856..89e66827 100644 --- a/local/pkgs/default.nix +++ b/local/pkgs/default.nix @@ -81,6 +81,7 @@ inputs: rec sbatch-tui = inputs.pkgs.callPackage ./sbatch-tui { inherit biu; }; ufo = inputs.pkgs.callPackage ./ufo { inherit concurrencpp biu glad matplotplusplus zpp-bits; tbb = inputs.pkgs.tbb_2021_11; }; + chn-bsub = inputs.pkgs.callPackage ./chn-bsub { inherit biu; }; fromYaml = content: builtins.fromJSON (builtins.readFile (inputs.pkgs.runCommand "toJSON" {}