1
0
Fork 0
mirror of https://github.com/transmission/transmission synced 2024-12-22 07:42:37 +00:00

build: support semver versioning (#3867)

* build: semver versioning

Xref: https://github.com/transmission/transmission/issues/1037

* test: add base62 tests for client-id

* build: include PATCH_VERSION in Transmission.rc.in

* build: semver versioning in version.h

* fixup! build: semver versioning in version.h

undo experimental verison changes that were made for testing purposes

* Fixup version in MSI package filename

Co-authored-by: Mike Gelfand <mikedld@mikedld.com>
This commit is contained in:
Charles Kerr 2022-10-05 16:53:10 -05:00 committed by GitHub
parent fff4c87740
commit 89d6533cd7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 150 additions and 59 deletions

View file

@ -69,26 +69,70 @@ tr_auto_option(WITH_SYSTEMD "Add support for systemd startup notificatio
set(TR_NAME ${PROJECT_NAME})
# convention: -TR MAJOR MINOR MAINT STATUS - (each a single char)
# STATUS: "X" for prerelease beta builds,
# "Z" for unsupported trunk builds,
# "0" for stable, supported releases
# these should be the only two lines you need to change
set(TR_USER_AGENT_PREFIX "3.00+")
set(TR_PEER_ID_PREFIX "-TR300Z-")
# major.minor.patch[-[beta.N.]dev]+commit_hash
# dev builds come between releases, e.g. autobuilds from CI
string(REGEX MATCH "^([0-9]+)\\.([0-9]+).*" TR_VERSION "${TR_USER_AGENT_PREFIX}")
set(TR_VERSION_MAJOR "${CMAKE_MATCH_1}")
set(TR_VERSION_MINOR "${CMAKE_MATCH_2}")
if(TR_PEER_ID_PREFIX MATCHES "X-$")
set(TR_BETA_RELEASE 1)
elseif(TR_PEER_ID_PREFIX MATCHES "Z-$")
# these should be the only five lines you need to change
set(TR_VERSION_MAJOR "3")
set(TR_VERSION_MINOR "0")
set(TR_VERSION_PATCH "0")
set(TR_VERSION_BETA_NUMBER "") # empty string for not beta
set(TR_VERSION_DEV TRUE)
# derived from above: release type
if (TR_VERSION_DEV)
set(TR_NIGHTLY_RELEASE 1)
elseif (NOT "${TR_VERSION_BETA_NUMBER}" STREQUAL "")
set(TR_BETA_RELEASE 1)
else()
set(TR_STABLE_RELEASE 1)
endif()
# derived from above: semver version string. https://semver.org/
# '4.0.0-beta.1'
# '4.0.0-beta.1.dev' (a dev release between beta 1 and 2)
# '4.0.0-beta.2'
# '4.0.0'
set(TR_SEMVER "${TR_VERSION_MAJOR}.${TR_VERSION_MINOR}.${TR_VERSION_PATCH}")
if (TR_VERSION_DEV OR NOT "${TR_VERSION_BETA_NUMBER}" STREQUAL "")
string(APPEND TR_SEMVER "-")
if (NOT "${TR_VERSION_BETA_NUMBER}" STREQUAL "")
string(APPEND TR_SEMVER "beta.${TR_VERSION_BETA_NUMBER}")
endif()
if (TR_VERSION_DEV AND NOT "${TR_VERSION_BETA_NUMBER}" STREQUAL "")
string(APPEND TR_SEMVER ".")
endif()
if (TR_VERSION_DEV)
string(APPEND TR_SEMVER "dev")
endif()
endif()
set(TR_USER_AGENT_PREFIX "${TR_SEMVER}")
# derived from above: peer-id prefix. https://www.bittorrent.org/beps/bep_0020.html
# chars 4, 5, 6 are major, minor, patch in https://en.wikipedia.org/wiki/Base62
# char 7 is '0' for a stable release, 'B' for a beta release, or 'Z' for a dev build
# '-TR400B-' (4.0.0 Beta)
# '-TR400Z-' (4.0.0 Dev)
# '-TR4000-' (4.0.0)
set(TR_PEER_ID_PREFIX "-TR")
set(BASE62 "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz")
string(SUBSTRING "${BASE62}" "${TR_VERSION_MAJOR}" 1 TMPSTR)
string(APPEND TR_PEER_ID_PREFIX "${TMPSTR}")
string(SUBSTRING "${BASE62}" "${TR_VERSION_MINOR}" 1 TMPSTR)
string(APPEND TR_PEER_ID_PREFIX "${TMPSTR}")
string(SUBSTRING "${BASE62}" "${TR_VERSION_PATCH}" 1 TMPSTR)
string(APPEND TR_PEER_ID_PREFIX "${TMPSTR}")
if (TR_VERSION_DEV)
string(APPEND TR_PEER_ID_PREFIX "Z")
elseif (NOT "${TR_VERSION_BETA_NUMBER}" STREQUAL "")
string(APPEND TR_PEER_ID_PREFIX "B")
else()
string(APPEND TR_PEER_ID_PREFIX "0")
endif()
string(APPEND TR_PEER_ID_PREFIX "-")
set(TR_VCS_REVISION_FILE "${CMAKE_SOURCE_DIR}/REVISION")
if(EXISTS ${CMAKE_SOURCE_DIR}/.git)
@ -692,9 +736,14 @@ if(MSVC AND ENABLE_DAEMON AND ENABLE_QT AND ENABLE_UTILS AND WITH_CRYPTO STREQUA
endif()
set(CPACK_SOURCE_GENERATOR TXZ)
set(CPACK_SOURCE_PACKAGE_FILE_NAME "${TR_NAME}-${TR_USER_AGENT_PREFIX}")
if(NOT TR_STABLE_RELEASE)
string(APPEND CPACK_SOURCE_PACKAGE_FILE_NAME "-r${TR_VCS_REVISION}")
set(CPACK_SOURCE_PACKAGE_FILE_NAME "${TR_NAME}-${TR_SEMVER}")
if(NOT TR_STABLE_RELEASE AND NOT "${TR_VCS_REVISION}" STREQUAL "")
# https://semver.org/#spec-item-11
# Build metadata MAY be denoted by appending a plus sign and a series of dot
# separated identifiers immediately following the patch or pre-release version.
# Identifiers MUST comprise only ASCII alphanumerics and hyphens [0-9A-Za-z-].
# Identifiers MUST NOT be empty.
string(APPEND CPACK_SOURCE_PACKAGE_FILE_NAME "+r${TR_VCS_REVISION}")
endif()
list(APPEND CPACK_SOURCE_IGNORE_FILES
"${CMAKE_BINARY_DIR}"

View file

@ -12,8 +12,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
#endif
VS_VERSION_INFO VERSIONINFO
FILEVERSION MAJOR_VERSION, MINOR_VERSION, 0, 0
PRODUCTVERSION MAJOR_VERSION, MINOR_VERSION, 0, 0
FILEVERSION MAJOR_VERSION, MINOR_VERSION, PATCH_VERSION, 0
PRODUCTVERSION MAJOR_VERSION, MINOR_VERSION, PATCH_VERSION, 0
FILEFLAGSMASK VS_FF_DEBUG | TR_FF_PRERELEASE
#ifdef _DEBUG
FILEFLAGS VS_FF_DEBUG | TR_FF_PRERELEASE

View file

@ -9,12 +9,12 @@ else()
endif()
set(VERSION "${TR_USER_AGENT_PREFIX}")
set(VERSION_MSI "${TR_VERSION_MAJOR}.${TR_VERSION_MINOR}.0")
set(VERSION_MSI "${TR_VERSION_MAJOR}.${TR_VERSION_MINOR}.${TR_VERSION_PATCH}")
set(VERSION_FULL "${TR_USER_AGENT_PREFIX} (${TR_VCS_REVISION})")
set(MSI_FILENAME_VERSION "${VERSION}")
if(TR_NIGHTLY_RELEASE)
set(MSI_FILENAME_VERSION "${MSI_FILENAME_VERSION}-r${TR_VCS_REVISION}")
set(MSI_FILENAME_VERSION "${TR_SEMVER}")
if(NOT TR_STABLE_RELEASE AND NOT "${TR_VCS_REVISION}" STREQUAL "")
string(APPEND MSI_FILENAME_VERSION "+r${TR_VCS_REVISION}")
endif()
if(NOT TR_THIRD_PARTY_DIR)

View file

@ -47,7 +47,7 @@ constexpr std::pair<char*, size_t> buf_append(char* buf, size_t buflen, T t, Arg
return buf_append(buf, buflen, args...);
}
constexpr std::string_view charint(uint8_t chr)
constexpr std::string_view base62str(uint8_t chr)
{
// clang-format off
auto constexpr Strings = std::array<std::string_view, 256>{
@ -60,10 +60,10 @@ constexpr std::string_view charint(uint8_t chr)
"x"sv, "x"sv, "x"sv, "x"sv, "x"sv, "10"sv, "11"sv, "12"sv, "13"sv, "14"sv,
"15"sv, "16"sv, "17"sv, "18"sv, "19"sv, "20"sv, "21"sv, "22"sv, "23"sv, "24"sv,
"25"sv, "26"sv, "27"sv, "28"sv, "29"sv, "30"sv, "31"sv, "32"sv, "33"sv, "34"sv,
"35"sv, "x"sv, "x"sv, "x"sv, "x"sv, "x"sv, "x"sv, "10"sv, "11"sv, "12"sv,
"13"sv, "14"sv, "15"sv, "16"sv, "17"sv, "18"sv, "19"sv, "20"sv, "21"sv, "22"sv,
"23"sv, "24"sv, "25"sv, "26"sv, "27"sv, "28"sv, "29"sv, "30"sv, "31"sv, "32"sv,
"33"sv, "34"sv, "35"sv, "x"sv, "x"sv, "x"sv, "x"sv, "x"sv, "x"sv, "x"sv,
"35"sv, "x"sv, "x"sv, "x"sv, "x"sv, "x"sv, "x"sv, "36"sv, "37"sv, "38"sv,
"39"sv, "40"sv, "41"sv, "42"sv, "43"sv, "44"sv, "45"sv, "46"sv, "47"sv, "48"sv,
"49"sv, "50"sv, "51"sv, "52"sv, "53"sv, "54"sv, "55"sv, "56"sv, "57"sv, "58"sv,
"59"sv, "60"sv, "61"sv, "x"sv, "x"sv, "x"sv, "x"sv, "x"sv, "x"sv, "x"sv,
"x"sv, "x"sv, "x"sv, "x"sv, "x"sv, "x"sv, "x"sv, "x"sv, "x"sv, "x"sv,
"x"sv, "x"sv, "x"sv, "x"sv, "x"sv, "x"sv, "x"sv, "x"sv, "x"sv, "x"sv,
"x"sv, "x"sv, "x"sv, "x"sv, "x"sv, "x"sv, "x"sv, "x"sv, "x"sv, "x"sv,
@ -89,7 +89,7 @@ int strint(char const* pch, int span, int base = 10)
return tr_parseNum<int>(sv, nullptr, base).value_or(0);
}
constexpr std::string_view getMnemonicEnd(uint8_t ch)
constexpr std::string_view utSuffix(uint8_t ch)
{
switch (ch)
{
@ -243,12 +243,12 @@ using format_func = void (*)(char* buf, size_t buflen, std::string_view name, tr
constexpr void three_digit_formatter(char* buf, size_t buflen, std::string_view name, tr_peer_id_t id)
{
buf_append(buf, buflen, name, ' ', charint(id[3]), '.', charint(id[4]), '.', charint(id[5]));
buf_append(buf, buflen, name, ' ', base62str(id[3]), '.', base62str(id[4]), '.', base62str(id[5]));
}
constexpr void four_digit_formatter(char* buf, size_t buflen, std::string_view name, tr_peer_id_t id)
{
buf_append(buf, buflen, name, ' ', charint(id[3]), '.', charint(id[4]), '.', charint(id[5]), '.', charint(id[6]));
buf_append(buf, buflen, name, ' ', base62str(id[3]), '.', base62str(id[4]), '.', base62str(id[5]), '.', base62str(id[6]));
}
void no_version_formatter(char* buf, size_t buflen, std::string_view name, tr_peer_id_t /*id*/)
@ -338,23 +338,23 @@ constexpr void burst_formatter(char* buf, size_t buflen, std::string_view name,
constexpr void ctorrent_formatter(char* buf, size_t buflen, std::string_view name, tr_peer_id_t id)
{
buf_append(buf, buflen, name, ' ', charint(id[3]), '.', charint(id[4]), '.', id[5], id[6]);
buf_append(buf, buflen, name, ' ', base62str(id[3]), '.', base62str(id[4]), '.', id[5], id[6]);
}
constexpr void folx_formatter(char* buf, size_t buflen, std::string_view name, tr_peer_id_t id)
{
buf_append(buf, buflen, name, ' ', charint(id[3]), '.', 'x');
buf_append(buf, buflen, name, ' ', base62str(id[3]), '.', 'x');
}
constexpr void ktorrent_formatter(char* buf, size_t buflen, std::string_view name, tr_peer_id_t id)
{
if (id[5] == 'D')
{
buf_append(buf, buflen, name, ' ', charint(id[3]), '.', charint(id[4]), " Dev "sv, charint(id[6]));
buf_append(buf, buflen, name, ' ', base62str(id[3]), '.', base62str(id[4]), " Dev "sv, base62str(id[6]));
}
else if (id[5] == 'R')
{
buf_append(buf, buflen, name, ' ', charint(id[3]), '.', charint(id[4]), " RC "sv, charint(id[6]));
buf_append(buf, buflen, name, ' ', base62str(id[3]), '.', base62str(id[4]), " RC "sv, base62str(id[6]));
}
else
{
@ -383,7 +383,7 @@ constexpr void mainline_formatter(char* buf, size_t buflen, std::string_view nam
constexpr void mediaget_formatter(char* buf, size_t buflen, std::string_view name, tr_peer_id_t id)
{
buf_append(buf, buflen, name, ' ', charint(id[3]), '.', charint(id[4]));
buf_append(buf, buflen, name, ' ', base62str(id[3]), '.', base62str(id[4]));
}
constexpr void mldonkey_formatter(char* buf, size_t buflen, std::string_view name, tr_peer_id_t id)
@ -405,7 +405,7 @@ constexpr void opera_formatter(char* buf, size_t buflen, std::string_view name,
constexpr void picotorrent_formatter(char* buf, size_t buflen, std::string_view name, tr_peer_id_t id)
{
buf_append(buf, buflen, name, ' ', charint(id[3]), '.', id[4], id[5], '.', charint(id[6]));
buf_append(buf, buflen, name, ' ', base62str(id[3]), '.', id[4], id[5], '.', base62str(id[6]));
}
constexpr void plus_formatter(char* buf, size_t buflen, std::string_view name, tr_peer_id_t id)
@ -415,7 +415,7 @@ constexpr void plus_formatter(char* buf, size_t buflen, std::string_view name, t
constexpr void qvod_formatter(char* buf, size_t buflen, std::string_view name, tr_peer_id_t id)
{
buf_append(buf, buflen, name, ' ', charint(id[4]), '.', charint(id[5]), '.', charint(id[6]), '.', charint(id[7]));
buf_append(buf, buflen, name, ' ', base62str(id[4]), '.', base62str(id[5]), '.', base62str(id[6]), '.', base62str(id[7]));
}
void transmission_formatter(char* buf, size_t buflen, std::string_view name, tr_peer_id_t id)
@ -426,21 +426,19 @@ void transmission_formatter(char* buf, size_t buflen, std::string_view name, tr_
{
*fmt::format_to_n(buf, buflen - 1, FMT_STRING("0.{:c}"), id[6]).out = '\0';
}
else if (std::equal(&id[3], &id[5], "00")) // previous client style: -TR0072- is 0.72
else if (std::equal(&id[3], &id[5], "00")) // pre-1.0 style: -TR0072- is 0.72
{
*fmt::format_to_n(buf, buflen - 1, FMT_STRING("0.{:02d}"), strint(&id[5], 2)).out = '\0';
}
else // current client style: -TR111Z- is 1.11+ */
else if (id[3] <= '3') // style up through 3.00: -TR111Z- is 1.11+
{
*fmt::format_to_n(
buf,
buflen - 1,
FMT_STRING("{:d}.{:02d}{:s}"),
strint(&id[3], 1),
strint(&id[4], 2),
(id[6] == 'Z' || id[6] == 'X') ? "+" : "")
*fmt::format_to_n(buf, buflen - 1, FMT_STRING("{:s}.{:02d}{:s}"), base62str(id[3]), strint(&id[4], 2), utSuffix(id[6]))
.out = '\0';
}
else // -TR400X- is 4.0.0 (Beta)"
{
buf_append(buf, buflen, base62str(id[3]), '.', base62str(id[4]), '.', base62str(id[5]), utSuffix(id[6]));
}
}
void utorrent_formatter(char* buf, size_t buflen, std::string_view name, tr_peer_id_t id)
@ -457,7 +455,7 @@ void utorrent_formatter(char* buf, size_t buflen, std::string_view name, tr_peer
strint(&id[4], 1, 16),
'.',
strint(&id[5], 1, 16),
getMnemonicEnd(id[6]));
utSuffix(id[6]));
}
else // uTorrent replaces the trailing dash with an extra digit for longer version numbers
{
@ -471,13 +469,13 @@ void utorrent_formatter(char* buf, size_t buflen, std::string_view name, tr_peer
strint(&id[4], 1, 16),
'.',
strint(&id[5], 2, 10),
getMnemonicEnd(id[7]));
utSuffix(id[7]));
}
}
constexpr void xbt_formatter(char* buf, size_t buflen, std::string_view name, tr_peer_id_t id)
{
buf_append(buf, buflen, name, ' ', id[3], '.', id[4], '.', id[5], getMnemonicEnd(id[6]));
buf_append(buf, buflen, name, ' ', id[3], '.', id[4], '.', id[5], utSuffix(id[6]));
}
constexpr void xfplay_formatter(char* buf, size_t buflen, std::string_view name, tr_peer_id_t id)
@ -494,7 +492,7 @@ constexpr void xfplay_formatter(char* buf, size_t buflen, std::string_view name,
void xtorrent_formatter(char* buf, size_t buflen, std::string_view name, tr_peer_id_t id)
{
std::tie(buf, buflen) = buf_append(buf, buflen, name, ' ', charint(id[3]), '.', charint(id[4]), " ("sv);
std::tie(buf, buflen) = buf_append(buf, buflen, name, ' ', base62str(id[3]), '.', base62str(id[4]), " ("sv);
*fmt::format_to_n(buf, buflen - 1, FMT_STRING("{:d}"), strint(&id[5], 2)).out = '\0';
}

View file

@ -7,9 +7,10 @@
#define SHORT_VERSION_STRING "${TR_USER_AGENT_PREFIX}"
#define LONG_VERSION_STRING "${TR_USER_AGENT_PREFIX} (${TR_VCS_REVISION})"
#define VERSION_STRING_INFOPLIST ${TR_USER_AGENT_PREFIX}
#define BUILD_STRING_INFOPLIST 14714.${TR_VERSION_MAJOR}.${TR_VERSION_MINOR}
#define BUILD_STRING_INFOPLIST 14714.${TR_VERSION_MAJOR}.${TR_VERSION_MINOR}.${TR_VERSION_PATCH}
#define MAJOR_VERSION ${TR_VERSION_MAJOR}
#define MINOR_VERSION ${TR_VERSION_MINOR}
#define PATCH_VERSION ${TR_VERSION_PATCH}
#cmakedefine TR_BETA_RELEASE 1
#cmakedefine TR_NIGHTLY_RELEASE 1

View file

@ -24,7 +24,7 @@ TEST(Client, clientForId)
std::string_view expected_client;
};
auto constexpr Tests = std::array<LocalTest, 44>{
auto constexpr Tests = std::array<LocalTest, 47>{
{ { "-ADB560-"sv, "Advanced Download Manager 11.5.6"sv },
{ "-AZ8421-"sv, "Azureus / Vuze 8.4.2.1"sv },
{ "-BC0241-"sv, "BitComet 2.41"sv }, // two major, two minor
@ -52,7 +52,10 @@ TEST(Client, clientForId)
{ "-TB2137-"sv, "Torch Browser"sv }, // Torch Browser 55.0.0.12137
{ "-TR0006-"sv, "Transmission 0.6"sv },
{ "-TR0072-"sv, "Transmission 0.72"sv },
{ "-TR111Z-"sv, "Transmission 1.11+"sv },
{ "-TR111Z-"sv, "Transmission 1.11 (Dev)"sv },
{ "-TR400B-"sv, "Transmission 4.0.0 (Beta)"sv },
{ "-TR4000-"sv, "Transmission 4.0.0"sv },
{ "-TR4Az0-"sv, "Transmission 4.10.61"sv },
{ "-UT341\0-"sv, "\xc2\xb5Torrent 3.4.1"sv },
{ "-UT7a5\0-"sv, "\xc2\xb5Torrent 7.10.5"sv },
{ "-UW110Q-"sv, "\xc2\xb5Torrent Web 1.1.0"sv },

View file

@ -1,4 +1,4 @@
#!/bin/sh
#!/bin/bash
# Generate files to be included: only overwrite them if changed so make
# won't rebuild everything unless necessary
@ -12,12 +12,51 @@ replace_if_differs() {
echo "creating libtransmission/version.h"
user_agent_prefix=$(grep 'set[(]TR_USER_AGENT_PREFIX' CMakeLists.txt | cut -d \" -f 2)
major_version=$(grep 'set[(]TR_VERSION_MAJOR' CMakeLists.txt | cut -d \" -f 2)
minor_version=$(grep 'set[(]TR_VERSION_MINOR' CMakeLists.txt | cut -d \" -f 2)
patch_version=$(grep 'set[(]TR_VERSION_PATCH' CMakeLists.txt | cut -d \" -f 2)
beta_number=$(grep 'set[(]TR_VERSION_BETA_NUMBER' CMakeLists.txt | cut -d \" -f 2)
if grep -q 'set[(]TR_VERSION_DEV TRUE)' CMakeLists.txt; then
is_dev=true
else
is_dev=false
fi
peer_id_prefix=$(grep 'set[(]TR_PEER_ID_PREFIX' CMakeLists.txt | cut -d \" -f 2)
# derived from above: semver version string. https://semver.org/
# '4.0.0-beta.1'
# '4.0.0-beta.1.dev' (a dev release between beta 1 and 2)
# '4.0.0-beta.2'
# '4.0.0'
user_agent_prefix="${major_version}.${minor_version}.${patch_version}"
if [ "$is_dev" = true ] || [ -n "${beta_number}" ]; then
user_agent_prefix="${user_agent_prefix}-"
if [ -n "${beta_number}" ]; then
user_agent_prefix="${user_agent_prefix}beta.${beta_number}"
fi
if [ "$is_dev" = true ] && [ -n "${beta_number}" ]; then
user_agent_prefix="${user_agent_prefix}."
fi
if [ "$is_dev" = true ]; then
user_agent_prefix="${user_agent_prefix}dev";
fi
fi
major_version=$(echo "${user_agent_prefix}" | awk -F . '{print $1}')
minor_version=$(echo "${user_agent_prefix}" | awk -F . '{print $2 + 0}')
# derived from above: peer-id prefix. https://www.bittorrent.org/beps/bep_0020.html
# chars 4, 5, 6 are major, minor, patch in https://en.wikipedia.org/wiki/Base62
# char 7 is '0' for a stable release, 'B' for a beta release, or 'Z' for a dev build
# '-TR400B-' (4.0.0 Beta)
# '-TR400Z-' (4.0.0 Dev)
# '-TR4000-' (4.0.0)
BASE62=($(echo {0..9} {A..A} {a..z}))
peer_id_prefix="-TR${BASE62[$(( 10#$major_version ))]}${BASE62[$(( 10#$minor_version ))]}${BASE62[$(( 10#$patch_version ))]}"
if [ "$is_dev" = true ]; then
peer_id_prefix="${peer_id_prefix}Z"
elif [ -n "${beta_number}" ]; then
peer_id_prefix="${peer_id_prefix}B"
else
peer_id_prefix="${peer_id_prefix}0"
fi
peer_id_prefix="${peer_id_prefix}-"
vcs_revision=
vcs_revision_file=REVISION
@ -51,9 +90,10 @@ cat > libtransmission/version.h.new << EOF
#define SHORT_VERSION_STRING "${user_agent_prefix}"
#define LONG_VERSION_STRING "${user_agent_prefix} (${vcs_revision})"
#define VERSION_STRING_INFOPLIST ${user_agent_prefix}
#define BUILD_STRING_INFOPLIST 14714.${major_version}.${minor_version}
#define BUILD_STRING_INFOPLIST 14714.${major_version}.${minor_version}.${patch_version}
#define MAJOR_VERSION ${major_version}
#define MINOR_VERSION ${minor_version}
#define PATCH_VERSION ${patch_version}
EOF
# Add a release definition