diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8d6aece..a09c6cb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -20,6 +20,11 @@ jobs: - uses: actions/checkout@v7 with: submodules: true + - id: engine_hashes + shell: bash + run: | + echo "official=$(git -C Stockfish rev-parse --short=12 HEAD)" >> "$GITHUB_OUTPUT" + echo "fairy=$(git -C Fairy-Stockfish rev-parse --short=12 HEAD)" >> "$GITHUB_OUTPUT" - uses: docker/setup-qemu-action@v4 with: platforms: arm64 @@ -46,6 +51,9 @@ jobs: cache-from: type=gha cache-to: type=gha,mode=max,ignore-error=true load: true + build-args: | + OFFICIAL_STOCKFISH_HASH=${{ steps.engine_hashes.outputs.official }} + FAIRY_STOCKFISH_HASH=${{ steps.engine_hashes.outputs.fairy }} - uses: docker/build-push-action@v7 id: docker_build_arm64 with: @@ -54,6 +62,9 @@ jobs: cache-from: type=gha cache-to: type=gha,mode=max,ignore-error=true load: true + build-args: | + OFFICIAL_STOCKFISH_HASH=${{ steps.engine_hashes.outputs.official }} + FAIRY_STOCKFISH_HASH=${{ steps.engine_hashes.outputs.fairy }} - uses: docker/login-action@v4 with: username: ${{ vars.DOCKERHUB_USERNAME }} @@ -79,6 +90,9 @@ jobs: push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} + build-args: | + OFFICIAL_STOCKFISH_HASH=${{ steps.engine_hashes.outputs.official }} + FAIRY_STOCKFISH_HASH=${{ steps.engine_hashes.outputs.fairy }} if: github.event_name == 'push' - run: docker cp "$(docker create ${{ steps.docker_build_amd64.outputs.imageid }}):/fishnet" fishnet-x86_64-unknown-linux-musl - run: docker cp "$(docker create ${{ steps.docker_build_arm64.outputs.imageid }}):/fishnet" fishnet-aarch64-unknown-linux-musl @@ -106,10 +120,14 @@ jobs: with: msystem: mingw64 update: true - install: mingw-w64-x86_64-sccache mingw-w64-x86_64-gcc mingw-w64-x86_64-rust mingw-w64-x86_64-make tar + install: git mingw-w64-x86_64-sccache mingw-w64-x86_64-gcc mingw-w64-x86_64-rust mingw-w64-x86_64-make tar - uses: actions/checkout@v7 with: submodules: true + - id: engine_hashes + run: | + echo "official=$(git -C Stockfish rev-parse --short=12 HEAD)" >> "$GITHUB_OUTPUT" + echo "fairy=$(git -C Fairy-Stockfish rev-parse --short=12 HEAD)" >> "$GITHUB_OUTPUT" - uses: actions/cache@v6 with: path: ${{ runner.temp }}/sccache @@ -128,7 +146,7 @@ jobs: continue-on-error: true - run: tar xf intel-sde/sde-external-9.0.0-2021-11-07-win.tar.xz && echo SDE_PATH=$(cygpath -u $GITHUB_WORKSPACE)/sde-external-9.0.0-2021-11-07-win/sde.exe | tee $GITHUB_ENV if: steps.sde.outcome == 'success' - - run: cargo build --release --target x86_64-pc-windows-gnu -vv + - run: OFFICIAL_STOCKFISH_HASH=${{ steps.engine_hashes.outputs.official }} FAIRY_STOCKFISH_HASH=${{ steps.engine_hashes.outputs.fairy }} cargo build --release --target x86_64-pc-windows-gnu -vv - run: mv target/x86_64-pc-windows-gnu/release/fishnet.exe fishnet-x86_64-pc-windows-gnu-unsigned.exe - uses: actions/upload-artifact@v7 id: upload-unsigned-artifact diff --git a/Cargo.lock b/Cargo.lock index 1fa7aed..8d39ad9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -336,7 +336,7 @@ checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "fishnet" -version = "2.14.0" +version = "2.14.1-dev" dependencies = [ "ar", "arrayvec", diff --git a/Dockerfile b/Dockerfile index e922994..834de45 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,6 +3,10 @@ ENV RUSTC_WRAPPER=/usr/bin/sccache ENV SCCACHE_DIR=/sccache ENV SCCACHE_CACHE_SIZE=250M WORKDIR /fishnet +ARG OFFICIAL_STOCKFISH_HASH +ARG FAIRY_STOCKFISH_HASH +ENV OFFICIAL_STOCKFISH_HASH=$OFFICIAL_STOCKFISH_HASH +ENV FAIRY_STOCKFISH_HASH=$FAIRY_STOCKFISH_HASH COPY . . RUN --mount=type=cache,target=/sccache sccache --show-stats && cargo auditable build --release -vv && sccache --show-stats diff --git a/build.rs b/build.rs index 109ca40..d9a991f 100644 --- a/build.rs +++ b/build.rs @@ -56,6 +56,7 @@ fn main() { "cargo:rustc-env=FISHNET_TARGET={}", env::var("TARGET").unwrap() ); + set_engine_hashes(); // Build Stockfish and Fairy-Stockfish and archive them // (along with eval files). @@ -76,6 +77,34 @@ fn main() { add_favicon(); } +fn set_engine_hashes() { + println!( + "cargo:rustc-env=OFFICIAL_STOCKFISH_HASH={}", + env::var("OFFICIAL_STOCKFISH_HASH") + .unwrap_or_else(|_| git("Stockfish", ["rev-parse", "--short=12", "HEAD"])) + ); + println!( + "cargo:rustc-env=FAIRY_STOCKFISH_HASH={}", + env::var("FAIRY_STOCKFISH_HASH") + .unwrap_or_else(|_| git("Fairy-Stockfish", ["rev-parse", "--short=12", "HEAD"])) + ); +} + +fn git(dir: &str, args: [&str; N]) -> String { + let output = Command::new("git") + .arg("-C") + .arg(dir) + .args(args) + .output() + .unwrap_or_else(|err| panic!("Could not inspect {dir}: {err}")); + assert!( + output.status.success(), + "Could not inspect engine revision in {dir}: {}", + String::from_utf8_lossy(&output.stderr) + ); + String::from_utf8(output.stdout).unwrap().trim().to_owned() +} + fn has_target_feature(feature: &str) -> bool { env::var("CARGO_CFG_TARGET_FEATURE") .unwrap() diff --git a/src/api.rs b/src/api.rs index 39199db..9a960d0 100644 --- a/src/api.rs +++ b/src/api.rs @@ -103,6 +103,7 @@ pub struct VoidRequestBody {} #[derive(Debug, Serialize)] struct Stockfish { flavor: EvalFlavor, + version: &'static str, } #[derive(Debug, Serialize)] @@ -681,7 +682,10 @@ impl ApiActor { slow: false, }) .json(&AnalysisRequestBody { - stockfish: Stockfish { flavor }, + stockfish: Stockfish { + flavor, + version: flavor.engine_version(), + }, analysis, }) .send() diff --git a/src/assets.rs b/src/assets.rs index 3a950a7..315eb40 100644 --- a/src/assets.rs +++ b/src/assets.rs @@ -4,6 +4,7 @@ use std::{ io, path::{Path, PathBuf}, str, + sync::OnceLock, }; use ar::Archive; @@ -147,6 +148,9 @@ pub enum EngineFlavor { MultiVariant, } +static OFFICIAL_STOCKFISH_VERSION: OnceLock = OnceLock::new(); +static FAIRY_STOCKFISH_VERSION: OnceLock = OnceLock::new(); + impl EngineFlavor { pub fn eval_flavor(self) -> EvalFlavor { match self { @@ -154,6 +158,30 @@ impl EngineFlavor { EngineFlavor::MultiVariant => EvalFlavor::Hce, } } + + pub fn set_version(self, version: String) { + let version_slot = match self { + EngineFlavor::Official => &OFFICIAL_STOCKFISH_VERSION, + EngineFlavor::MultiVariant => &FAIRY_STOCKFISH_VERSION, + }; + version_slot.get_or_init(|| match self { + EngineFlavor::Official => format!( + "OfficialStockfish/{version}/{}", + env!("OFFICIAL_STOCKFISH_HASH") + ), + EngineFlavor::MultiVariant => { + format!("FairyStockfish/{version}/{}", env!("FAIRY_STOCKFISH_HASH")) + } + }); + } + + pub fn version(self) -> &'static str { + let version_slot = match self { + EngineFlavor::Official => &OFFICIAL_STOCKFISH_VERSION, + EngineFlavor::MultiVariant => &FAIRY_STOCKFISH_VERSION, + }; + version_slot.get().expect("engine UCI initialized").as_str() + } } #[derive(Debug, Default)] @@ -194,6 +222,13 @@ impl EvalFlavor { pub fn is_hce(self) -> bool { matches!(self, EvalFlavor::Hce) } + + pub fn engine_version(self) -> &'static str { + match self { + EvalFlavor::Hce => EngineFlavor::MultiVariant.version(), + EvalFlavor::Nnue => EngineFlavor::Official.version(), + } + } } #[derive(Debug)] diff --git a/src/main.rs b/src/main.rs index 6011963..e1b9e98 100644 --- a/src/main.rs +++ b/src/main.rs @@ -273,33 +273,35 @@ async fn worker(i: usize, assets: Arc, tx: mpsc::Sender, logger: L // Ensure engine process is ready. let flavor = chunk.flavor; let context = ProgressAt::from(&chunk); - let (mut sf, join_handle) = if let Some((sf, join_handle)) = - engine.get_mut(flavor).take() - { - (sf, join_handle) - } else { - // Backoff before starting engine. - let backoff = engine_backoff.next(); - if backoff >= Duration::from_secs(5) { - logger.info(&format!( - "Waiting {backoff:?} before attempting to start engine" - )); + let (mut sf, join_handle) = + if let Some((sf, join_handle)) = engine.get_mut(flavor).take() { + (sf, join_handle) } else { - logger.debug(&format!( - "Waiting {backoff:?} before attempting to start engine" - )); - } - tokio::select! { - _ = tx.closed() => break, - _ = sleep(engine_backoff.next()) => (), - } + // Backoff before starting engine. + let backoff = engine_backoff.next(); + if backoff >= Duration::from_secs(5) { + logger.info(&format!( + "Waiting {backoff:?} before attempting to start engine" + )); + } else { + logger.debug(&format!( + "Waiting {backoff:?} before attempting to start engine" + )); + } + tokio::select! { + _ = tx.closed() => break, + _ = sleep(engine_backoff.next()) => (), + } - // Start engine and spawn actor. - let (sf, sf_actor) = - stockfish::channel(assets.stockfish.get(flavor).path.clone(), logger.clone()); - let join_handle = tokio::spawn(sf_actor.run()); - (sf, join_handle) - }; + // Start engine and spawn actor. + let (sf, sf_actor) = stockfish::channel( + flavor, + assets.stockfish.get(flavor).path.clone(), + logger.clone(), + ); + let join_handle = tokio::spawn(sf_actor.run()); + (sf, join_handle) + }; // Analyse or play. let batch_id = chunk.work.id(); diff --git a/src/stockfish.rs b/src/stockfish.rs index ce1c4eb..0b12cab 100644 --- a/src/stockfish.rs +++ b/src/stockfish.rs @@ -15,12 +15,17 @@ use crate::{ util::NevermindExt as _, }; -pub fn channel(exe: PathBuf, logger: Logger) -> (StockfishStub, StockfishActor) { +pub fn channel( + flavor: EngineFlavor, + exe: PathBuf, + logger: Logger, +) -> (StockfishStub, StockfishActor) { let (tx, rx) = mpsc::channel(1); ( StockfishStub { tx }, StockfishActor { rx, + flavor, exe, initialized: false, logger, @@ -49,6 +54,7 @@ impl StockfishStub { pub struct StockfishActor { rx: mpsc::Receiver, + flavor: EngineFlavor, exe: PathBuf, initialized: bool, logger: Logger, @@ -211,6 +217,21 @@ impl StockfishActor { async fn init(&mut self, stdout: &mut Stdout, stdin: &mut Stdin) -> io::Result<()> { if !mem::replace(&mut self.initialized, true) { + stdin.write_line("uci").await?; + stdin.flush().await?; + + let mut version = "unknown".to_owned(); + loop { + let line = stdout.read_line().await?; + if let Some(name) = line.strip_prefix("id name ") { + version = name.to_owned(); + } + if line.trim_end() == "uciok" { + break; + } + } + self.flavor.set_version(version.replace('/', "_")); + stdin .write_line("setoption name UCI_Chess960 value true") .await?;