Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING "Build type" FORCE)
endif()

# One source of truth for the version: PROJECT_VERSION above. Everything that
# needs it reads the generated header rather than repeating the number.
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/src/core/version.hpp.in"
"${CMAKE_CURRENT_BINARY_DIR}/generated/core/version.hpp" @ONLY)

option(WSLDRIVE_BUILD_TESTS "Build unit tests" ON)
option(WSLDRIVE_BUILD_BENCHMARKS "Build micro-benchmarks" ON)
option(WSLDRIVE_WARNINGS_AS_ERRORS "Treat compiler and linker warnings as errors" ON)
Expand Down
11 changes: 10 additions & 1 deletion scripts/build-installer.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,21 @@
[CmdletBinding()]
param(
[string] $Config = 'release',
[string] $Version = '0.1.0',
# Defaults to the project version in CMakeLists.txt. Hard-coding it here is
# how the script came to stamp 0.1.0 onto a 0.9.0 build.
[string] $Version = '',
[switch] $InstallInno
)

$ErrorActionPreference = 'Stop'
$root = Split-Path -Parent $PSScriptRoot

if (-not $Version) {
$cml = Get-Content (Join-Path $root 'CMakeLists.txt') -Raw
if ($cml -match '(?m)^\s*VERSION\s+(\d+\.\d+\.\d+)\s*$') { $Version = $Matches[1] }
else { throw "could not read VERSION from CMakeLists.txt; pass -Version explicitly" }
}
Write-Host "building installer for wsldrive $Version"
$iss = Join-Path $root 'installer\wsldrive.iss'
$binDir = Join-Path $root "build\msvc-$Config\src\tools"
$linux = Join-Path $root 'build\linux-release\src\tools\wsldrive' # client (Direction B)
Expand Down
6 changes: 5 additions & 1 deletion src/agent/client.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "agent/client.hpp"

#include "core/path.hpp"
#include "core/version.hpp"

#include <algorithm>
#include <chrono>
Expand Down Expand Up @@ -62,7 +63,10 @@ Result<proto::Hello> RemoteRoot::connect(std::chrono::milliseconds timeout) {
proto::Writer w(payload);
proto::write_hello(
w, proto::Hello{
.protocol_version = proto::kVersion, .capabilities = 0, .agent = "wsldrive/0.1", .token = token_});
.protocol_version = proto::kVersion,
.capabilities = 0,
.agent = agent_string("wsldrive"),
.token = token_});
auto p = request(proto::MsgType::Hello, payload, timeout);
if (!p) return fail(p.error());
// An Error reply (e.g. a rejected token) is already decoded by request().
Expand Down
3 changes: 2 additions & 1 deletion src/agent/server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include "agent/scanner.hpp"
#include "core/path.hpp"
#include "core/version.hpp"
#ifdef _WIN32
#include "platform/win/wide.hpp"
#endif
Expand Down Expand Up @@ -350,7 +351,7 @@ Result<void> RootServer::handle(const net::Frame& f, net::FrameChannel& ch, bool
return ch.send_with(proto::MsgType::HelloAck, f.header.request_id, [&](proto::Writer& w) {
proto::write_hello(w, proto::Hello{.protocol_version = proto::kVersion,
.capabilities = watching() ? 1u : 0u,
.agent = "wsldrived/0.1", .token = {}});
.agent = agent_string("wsldrived"), .token = {}});
});
}
case proto::MsgType::Ping: return ch.send(proto::MsgType::Pong, f.header.request_id, {});
Expand Down
2 changes: 1 addition & 1 deletion src/core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ add_library(wsldrive_core STATIC
add_library(wsldrive::core ALIAS wsldrive_core)

target_include_directories(wsldrive_core
PUBLIC "${CMAKE_SOURCE_DIR}/src"
PUBLIC "${CMAKE_SOURCE_DIR}/src" "${CMAKE_BINARY_DIR}/generated"
PRIVATE "${WSLDRIVE_BLAKE3_INCLUDE_DIR}")
target_link_libraries(wsldrive_core
PUBLIC wsldrive::options
Expand Down
30 changes: 30 additions & 0 deletions src/core/version.hpp.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#pragma once

#include <string>
#include <string_view>

// Generated from src/core/version.hpp.in by CMake. The project version has one
// source of truth - PROJECT_VERSION in the top-level CMakeLists.txt - and this
// is how the rest of the build reads it: `--version` and the usage banners, the
// handshake agent strings, the Windows VERSIONINFO resource, and the default
// version stamped into the installer.

namespace wsld {

/// Project version, e.g. "0.9.0".
inline constexpr std::string_view kVersionString = "@PROJECT_VERSION@";

inline constexpr int kVersionMajor = @PROJECT_VERSION_MAJOR@;
inline constexpr int kVersionMinor = @PROJECT_VERSION_MINOR@;
inline constexpr int kVersionPatch = @PROJECT_VERSION_PATCH@;

/// "<name>/<version>", for the free-form agent field of the Hello handshake.
/// Returns a reference to a function-local static, so the string_view a caller
/// stores in a message stays valid for the life of the process.
inline const std::string& agent_string(std::string_view name) {
static const std::string wsldrive_agent = "wsldrive/" + std::string(kVersionString);
static const std::string wsldrived_agent = "wsldrived/" + std::string(kVersionString);
return name == "wsldrived" ? wsldrived_agent : wsldrive_agent;
}

} // namespace wsld
22 changes: 22 additions & 0 deletions src/tools/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,25 @@ endif()
if(WIN32)
add_executable(wsldrivew WIN32 wsldrivew_main.cpp)
endif()

# Stamp each executable with a VERSIONINFO resource, from the same
# PROJECT_VERSION the binaries print. Without it Explorer's Details tab and
# (Get-Item wsldrive.exe).VersionInfo say nothing about which build this is.
if(WIN32)
enable_language(RC)
foreach(tgt wsldrive wsldrived wsldrivew)
if(TARGET ${tgt})
set(WSLDRIVE_RC_NAME "${tgt}")
if(tgt STREQUAL "wsldrived")
set(WSLDRIVE_RC_DESCRIPTION "wsldrive agent (serves a directory tree)")
elseif(tgt STREQUAL "wsldrivew")
set(WSLDRIVE_RC_DESCRIPTION "wsldrive windowless launcher")
else()
set(WSLDRIVE_RC_DESCRIPTION "wsldrive command line tool")
endif()
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/version.rc.in"
"${CMAKE_CURRENT_BINARY_DIR}/${tgt}_version.rc" @ONLY)
target_sources(${tgt} PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/${tgt}_version.rc")
endif()
endforeach()
endif()
34 changes: 34 additions & 0 deletions src/tools/version.rc.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Generated from version.rc.in by CMake, once per executable. Without it
// Explorer's Details tab and `(Get-Item wsldrive.exe).VersionInfo` report
// nothing, so a user cannot tell which build they are running - and neither can
// a support request.
#include <winver.h>

VS_VERSION_INFO VERSIONINFO
FILEVERSION @PROJECT_VERSION_MAJOR@,@PROJECT_VERSION_MINOR@,@PROJECT_VERSION_PATCH@,0
PRODUCTVERSION @PROJECT_VERSION_MAJOR@,@PROJECT_VERSION_MINOR@,@PROJECT_VERSION_PATCH@,0
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
FILEFLAGS 0
FILEOS VOS_NT_WINDOWS32
FILETYPE VFT_APP
FILESUBTYPE VFT2_UNKNOWN
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904B0" // U.S. English, Unicode
BEGIN
VALUE "CompanyName", "wsldrive"
VALUE "FileDescription", "@WSLDRIVE_RC_DESCRIPTION@"
VALUE "FileVersion", "@PROJECT_VERSION@"
VALUE "InternalName", "@WSLDRIVE_RC_NAME@"
VALUE "OriginalFilename", "@WSLDRIVE_RC_NAME@.exe"
VALUE "ProductName", "wsldrive"
VALUE "ProductVersion", "@PROJECT_VERSION@"
VALUE "LegalCopyright", "MIT licensed"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1200
END
END
16 changes: 16 additions & 0 deletions src/tools/wsldrive_main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,19 @@
#include <chrono>
#include <cstdio>
#include <memory>
#include "core/version.hpp"

#include <random>
#include <string>
#include <thread>
#include <vector>

namespace {

void print_version(const char* who) { std::printf("%s %s\n", who, std::string(wsld::kVersionString).c_str()); }

void usage() {
std::fprintf(stderr, "wsldrive %s\n", std::string(wsld::kVersionString).c_str());
std::fputs(
"usage:\n"
" wsldrive fetch (--connect <endpoint> | --listen <endpoint>) [--watch] [--read <path>] [--lookups N]\n"
Expand All @@ -53,6 +58,7 @@ void usage() {
" (auto-launch the agent in the distro)\n"
" add --hvsocket [--vm-guid <guid>] (use the Hyper-V socket transport)\n"
#endif
" wsldrive --version\n"
,
stderr);
}
Expand Down Expand Up @@ -85,6 +91,16 @@ void install_mount_signal_handler() {
} // namespace

int main(int argc, char** argv) {
// Answered before anything else, so it still works on a machine where the
// rest of the environment (WinFsp, a distro, a reachable agent) is not set
// up - which is exactly when someone needs to know which build they have.
if (argc >= 2) {
const std::string_view first = argv[1];
if (first == "--version" || first == "-V" || first == "version") {
print_version("wsldrive");
return 0;
}
}
const std::string_view command = argc >= 2 ? std::string_view(argv[1]) : std::string_view{};

#ifdef _WIN32
Expand Down
12 changes: 11 additions & 1 deletion src/tools/wsldrived_main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "agent/server.hpp"

#include "core/auth_token.hpp"
#include "core/version.hpp"
#include "net/frame_channel.hpp"
#include "net/socket.hpp"

Expand All @@ -19,19 +20,28 @@
namespace {

void usage() {
std::fprintf(stderr, "wsldrived %s\n", std::string(wsld::kVersionString).c_str());
std::fputs(
"usage: wsldrived --root <dir> (--listen <endpoint> | --connect <endpoint>) [--no-watch] [--exit-when-idle]\n"
" [--cross-filesystems]\n"
" endpoints: tcp://host:port | vsock://cid:port | hv://port\n"
" --exit-when-idle: serve a single client session then exit (used by auto-launch)\n"
" --cross-filesystems: also descend into mount points below --root. Off by default, so\n"
" serving / skips /proc, /sys, /dev, /run and foreign mounts like /mnt/c.\n",
" serving / skips /proc, /sys, /dev, /run and foreign mounts like /mnt/c.\n"
" --version: print the version and exit.\n",
stderr);
}

} // namespace

int main(int argc, char** argv) {
if (argc >= 2) {
const std::string_view first = argv[1];
if (first == "--version" || first == "-V") {
std::printf("wsldrived %s\n", std::string(wsld::kVersionString).c_str());
return 0;
}
}
std::filesystem::path root;
std::string listen, connect;
bool watch = true;
Expand Down
1 change: 1 addition & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ add_executable(core_tests
protocol_test.cpp
string_pool_test.cpp
unicode_test.cpp
version_test.cpp
win_names_test.cpp)

target_link_libraries(core_tests PRIVATE wsldrive::core GTest::gtest_main)
Expand Down
31 changes: 31 additions & 0 deletions tests/version_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#include "core/version.hpp"

#include <gtest/gtest.h>

#include <string>
#include <string_view>

namespace wsld {
namespace {

TEST(Version, IsWiredToTheProjectVersion) {
// The version used to be written out by hand in several places and drifted:
// the handshake said 0.1, the installer defaulted to 0.1.0, and the project
// was 0.9.0. Everything reads the generated header now, so this only has to
// check that the header carries a real version and that the agent strings are
// built from it rather than repeating a literal.
EXPECT_FALSE(kVersionString.empty());
EXPECT_NE(kVersionString, "0.0.0");
EXPECT_EQ(kVersionString.find_first_not_of("0123456789."), std::string_view::npos)
<< "expected a dotted numeric version, got: " << kVersionString;
EXPECT_EQ(kVersionString, std::to_string(kVersionMajor) + "." + std::to_string(kVersionMinor) + "." +
std::to_string(kVersionPatch));

EXPECT_EQ(agent_string("wsldrive"), "wsldrive/" + std::string(kVersionString));
EXPECT_EQ(agent_string("wsldrived"), "wsldrived/" + std::string(kVersionString));
// The handshake stores a view into this, so it has to outlive the call.
EXPECT_EQ(&agent_string("wsldrive"), &agent_string("wsldrive"));
}

} // namespace
} // namespace wsld
Loading