Files
mpd/src/command/PartitionCommands.cxx
Rudi Heitbaum 9db7144d0f lib/fmt: drop use of FMT_STRING
When compiling with libfmt-11.1.0 and newer the following compile errors occur:

In file included from ../src/decoder/DecoderPrint.cxx:23:
../src/client/Response.hxx: In instantiation of 'bool Response::Fmt(const S&, Args&& ...) [with S = decoder_plugin_print(Response&, const DecoderPlugin&)::<lambda()>::FMT_COMPILE_STRING; Args = {const char* const&}]':
../src/decoder/DecoderPrint.cxx:38:7:   required from here
   38 |         r.Fmt(FMT_STRING("plugin: {}\n"), plugin.name);
      |         ~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
../src/client/Response.hxx:86:28: error: cannot convert 'const decoder_plugin_print(Response&, const DecoderPlugin&)::<lambda()>::FMT_COMPILE_STRING' to 'fmt::v11::string_view' {aka 'fmt::v11::basic_string_view<char>'}
   86 |                 return VFmt(format_str,
      |                        ~~~~^~~~~~~~~~~~
   87 |                             fmt::make_format_args(args...));
      |                             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
../src/client/Response.hxx:81:36: note: initializing argument 1 of 'bool Response::VFmt(fmt::v11::string_view, fmt::v11::format_args)'
   81 |         bool VFmt(fmt::string_view format_str, fmt::format_args args) noexcept;
      |                   ~~~~~~~~~~~~~~~~~^~~~~~~~~~
../src/client/Response.hxx: In instantiation of 'bool Response::Fmt(const S&, Args&& ...) [with S = decoder_plugin_print(Response&, const DecoderPlugin&)::<lambda()>::FMT_COMPILE_STRING; Args = {const char* const&}]':

The error is due to the use of FMT_STRING. The libfmt team shared the following:

    The correct way of using FMT_STRING is to wrap a format string when passing to a
    function with compile-time checks (i.e. that takes format_string) as documented
    in https://fmt.dev/11.1/api/#legacy-compile-time-checks.

    Noting that FMT_STRING is a legacy API and has been superseded by consteval-based
    API starting from version 8: https://github.com/fmtlib/fmt/releases/tag/8.0.0. It
    looks like MPD is trying to emulate {fmt}'s old way of implementing compile-time
    checks which was never properly documented because it was basically a hack. So the
    correct fix is to switch to format_string and, possibly, remove usage of FMT_STRING.

    The old way of doing compile-time checks (fmt::make_args_checked) was documented
    in https://fmt.dev/7.1/api.html#argument-lists but it looks like MPD is not using
    that API so the problematic uses of FMT_STRING have no effect and can just be removed.

The FMT_STRING has been removed in this change based on the fmt-7.1 API and now MPD is
successfully compile against the current libfmt-11.1.0 which highlighted the issue that
had been present in the codebase as it is now triggering the error, is legacy and was
not using the API for which FMT_STRING was aligned with.
2025-01-13 06:17:01 +00:00

199 lines
5.3 KiB
C++

/*
* Copyright 2003-2021 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "PartitionCommands.hxx"
#include "Request.hxx"
#include "Instance.hxx"
#include "Partition.hxx"
#include "IdleFlags.hxx"
#include "output/Filtered.hxx"
#include "client/Client.hxx"
#include "client/Response.hxx"
#include "util/CharUtil.hxx"
#include <fmt/format.h>
CommandResult
handle_partition(Client &client, Request request, Response &response)
{
const char *name = request.front();
auto &instance = client.GetInstance();
auto *partition = instance.FindPartition(name);
if (partition == nullptr) {
response.Error(ACK_ERROR_NO_EXIST, "partition does not exist");
return CommandResult::ERROR;
}
client.SetPartition(*partition);
return CommandResult::OK;
}
CommandResult
handle_listpartitions(Client &client, Request, Response &r)
{
for (const auto &partition : client.GetInstance().partitions) {
r.Fmt("partition: {}\n", partition.name);
}
return CommandResult::OK;
}
static constexpr bool
IsValidPartitionChar(char ch)
{
return IsAlphaNumericASCII(ch) || ch == '-' || ch == '_';
}
gcc_pure
static bool
IsValidPartitionName(const char *name) noexcept
{
do {
if (!IsValidPartitionChar(*name))
return false;
} while (*++name != 0);
return true;
}
gcc_pure
static bool
HasPartitionNamed(Instance &instance, const char *name) noexcept
{
return instance.FindPartition(name) != nullptr;
}
CommandResult
handle_newpartition(Client &client, Request request, Response &response)
{
const char *name = request.front();
if (!IsValidPartitionName(name)) {
response.Error(ACK_ERROR_ARG, "bad name");
return CommandResult::ERROR;
}
auto &instance = client.GetInstance();
if (HasPartitionNamed(instance, name)) {
response.Error(ACK_ERROR_EXIST, "name already exists");
return CommandResult::ERROR;
}
if (instance.partitions.size() >= 16) {
/* arbitrary limit for now */
response.Error(ACK_ERROR_UNKNOWN, "too many partitions");
return CommandResult::ERROR;
}
instance.partitions.emplace_back(instance, name,
// TODO: use real configuration
16384,
1024,
AudioFormat::Undefined(),
ReplayGainConfig());
auto &partition = instance.partitions.back();
partition.UpdateEffectiveReplayGainMode();
instance.EmitIdle(IDLE_PARTITION);
return CommandResult::OK;
}
CommandResult
handle_delpartition(Client &client, Request request, Response &response)
{
const char *name = request.front();
if (!IsValidPartitionName(name)) {
response.Error(ACK_ERROR_ARG, "bad name");
return CommandResult::ERROR;
}
auto &instance = client.GetInstance();
auto *partition = instance.FindPartition(name);
if (partition == nullptr) {
response.Error(ACK_ERROR_NO_EXIST, "no such partition");
return CommandResult::ERROR;
}
if (partition == &instance.partitions.front()) {
response.Error(ACK_ERROR_UNKNOWN,
"cannot delete the default partition");
return CommandResult::ERROR;
}
if (!partition->clients.empty()) {
response.Error(ACK_ERROR_UNKNOWN,
"partition still has clients");
return CommandResult::ERROR;
}
if (!partition->outputs.IsDummy()) {
response.Error(ACK_ERROR_UNKNOWN,
"partition still has outputs");
return CommandResult::ERROR;
}
partition->BeginShutdown();
instance.DeletePartition(*partition);
instance.EmitIdle(IDLE_PARTITION);
return CommandResult::OK;
}
CommandResult
handle_moveoutput(Client &client, Request request, Response &response)
{
const char *output_name = request[0];
auto &dest_partition = client.GetPartition();
auto *existing_output = dest_partition.outputs.FindByName(output_name);
if (existing_output != nullptr && !existing_output->IsDummy())
/* this output is already in the specified partition,
so nothing needs to be done */
return CommandResult::OK;
/* find the partition which owns this output currently */
auto &instance = client.GetInstance();
for (auto &partition : instance.partitions) {
if (&partition == &dest_partition)
continue;
auto *output = partition.outputs.FindByName(output_name);
if (output == nullptr || output->IsDummy())
continue;
const bool was_enabled = output->IsEnabled();
if (existing_output != nullptr)
/* move the output back where it once was */
existing_output->ReplaceDummy(output->Steal(),
was_enabled);
else
/* copy the AudioOutputControl and add it to the output list */
dest_partition.outputs.AddMoveFrom(std::move(*output),
was_enabled);
instance.EmitIdle(IDLE_OUTPUT);
return CommandResult::OK;
}
response.Error(ACK_ERROR_NO_EXIST, "No such output");
return CommandResult::ERROR;
}