【问题标题】:Create version number variations for info.plist using #define and clang?使用 #define 和 clang 为 info.plist 创建版本号变体?
【发布时间】:2016-06-30 07:07:52
【问题描述】:

几年前,当使用 GCC 编译时,#include .h 文件中的以下定义可以被预处理以用于 info.plist:

#define MAJORVERSION 2
#define MINORVERSION 6 
#define MAINTVERSION 4

<key>CFBundleShortVersionString</key> <string>MAJORVERSION.MINORVERSION.MAINTVERSION</string>

...这将变成“2.6.4”。这很有效,因为 GCC 支持“-traditional”标志。 (参见Tech Note TN2175 Info.plist files in Xcode Using the C Preprocessor,在“在宏扩展过程中消除标记之间的空白”下)

但是,快进到 2016 年和 Clang 7.0.2 (Xcode 7.2.1) 显然不支持“-traditional”或“-traditional-cpp”(或正确支持),产生以下字符串:

"2 . 6 . 4"

(见Bug 12035 - Preprocessor inserts spaces in macro expansions, comment 4

因为有这么多不同的变体(CFBundleShortVersionString、CFBundleVersion、CFBundleGetInfoString),解决这个clang 问题并定义一次,然后将这些片段连接/串起来会很好。现在这样做的普遍接受的模式是什么? (我目前在 MacOS 上构建,但同样的模式适用于 IOS)

【问题讨论】:

  • 除了示例代码中的拼写错误外,它可以正常工作 - 相邻的字符串被连接起来,并且没有添加空格(在 Xcode 7.2 中测试)。如果您看到空间,您可能需要提供更多上下文,然后才能帮助您弄清楚。
  • @CRD -- 谢谢 -- 我重写了示例以准确反映真正的问题,而不是我如何拼凑出一个潜在的解决方案。
  • 我总是将版本号存储在一个外部文件中,并有一个预构建脚本(用 python 编写)来更新 plist.info 文件并生成任何要包含在项目中的 version.h 文件.它简单可靠,甚至可以跨平台(即您可以修改它以写入 Windows 资源文件)。如果它检测到对任何源文件的更改,它也会增加内部版本号,因此它主要管理自己。
  • @trojanfoe - 这是为了解决这个带有 -traditional 标志的错误吗?你能分享一个你的 Python 脚本示例吗?
  • 不,这不是为了解决这个问题,因为我从来没有考虑过使用这样的东西来管理版本号。我已经在下面发布了我的代码作为答案,但是这个版本不会生成 version.h 文件(这个脚本有很多次迭代)。

标签: c++ ios xcode macos clang


【解决方案1】:

这是我用来增加内部版本号的 Python 脚本,只要检测到源代码更改,并更新项目中的一个或多个 Info.plist 文件。

它是为了解决this question 中提出的问题而创建的,我不久前问过。

您需要在源代码树中创建buildnum.ver 文件,如下所示:

version 1.0
build 1

(当达到某些项目里程碑时,您需要手动增加 version,但 buildnum 会自动增加)。

注意.ver 文件的位置必须位于源代码树的根目录中(请参阅下面的SourceDir),因为此脚本将在此目录中查找修改后的文件。如果找到任何内容,则内部版本号会增加。已修改表示在上次更新 .ver 文件后源文件发生更改。

然后创建一个新的 Xcode 目标来运行外部构建工具并运行类似的东西:

tools/bump_buildnum.py SourceDir/buildnum.ver SourceDir/Info.plist

(让它在${PROJECT_DIR}中运行)

然后让所有实际的 Xcode 目标依赖于这个目标,所以它在任何一个构建之前运行。

#!/usr/bin/env python
#
# Bump build number in Info.plist files if a source file have changed.
#
# usage: bump_buildnum.py buildnum.ver Info.plist [ ... Info.plist ]
#
# andy@trojanfoe.com, 2014.
#

import sys, os, subprocess, re

def read_verfile(name):
    version = None
    build = None
    verfile = open(name, "r")
    for line in verfile:
        match = re.match(r"^version\s+(\S+)", line)
        if match:
            version = match.group(1).rstrip()
        match = re.match(r"^build\s+(\S+)", line)
        if match:
            build = int(match.group(1).rstrip())
    verfile.close()
    return (version, build)

def write_verfile(name, version, build):
    verfile = open(name, "w")
    verfile.write("version {0}\n".format(version))
    verfile.write("build {0}\n".format(build))
    verfile.close()
    return True

def set_plist_version(plistname, version, build):
    if not os.path.exists(plistname):
        print("{0} does not exist".format(plistname))
        return False

    plistbuddy = '/usr/libexec/Plistbuddy'
    if not os.path.exists(plistbuddy):
        print("{0} does not exist".format(plistbuddy))
        return False

    cmdline = [plistbuddy,
        "-c", "Set CFBundleShortVersionString {0}".format(version),
        "-c", "Set CFBundleVersion {0}".format(build),
        plistname]
    if subprocess.call(cmdline) != 0:
        print("Failed to update {0}".format(plistname))
        return False

    print("Updated {0} with v{1} ({2})".format(plistname, version, build))
    return True

def should_bump(vername, dirname):
    verstat = os.stat(vername)
    allnames = []
    for dirname, dirnames, filenames in os.walk(dirname):
        for filename in filenames:
            allnames.append(os.path.join(dirname, filename))

    for filename in allnames:
        filestat = os.stat(filename)
        if filestat.st_mtime > verstat.st_mtime:
            print("{0} is newer than {1}".format(filename, vername))
            return True

    return False

def upver(vername):
    (version, build) = read_verfile(vername)
    if version == None or build == None:
        print("Failed to read version/build from {0}".format(vername))
        return False

    # Bump the version number if any files in the same directory as the version file
    # have changed, including sub-directories.
    srcdir = os.path.dirname(vername)
    bump = should_bump(vername, srcdir)

    if bump:
        build += 1
        print("Incremented to build {0}".format(build))
        write_verfile(vername, version, build)
        print("Written {0}".format(vername))
    else:
        print("Staying at build {0}".format(build))

    return (version, build)

if __name__ == "__main__":
    if os.environ.has_key('ACTION') and os.environ['ACTION'] == 'clean':
        print("{0}: Not running while cleaning".format(sys.argv[0]))
        sys.exit(0)

    if len(sys.argv) < 3:
        print("Usage: {0} buildnum.ver Info.plist [... Info.plist]".format(sys.argv[0]))
        sys.exit(1)
    vername = sys.argv[1]

    (version, build) = upver(vername)
    if version == None or build == None:
        sys.exit(2)

    for i in range(2, len(sys.argv)):
        plistname = sys.argv[i]
        set_plist_version(plistname, version, build)        

    sys.exit(0)

【讨论】:

  • 哇!优雅和教育!您描述了编写 version.h 的可能性——在我的例子中,我在各种 .cpp 文件中引用了这些构建和版本号。它是否也可以通过解析 version.h 文件中的版本组件来工作,还是重写 version.h 更容易?
  • 另外——Apple 的版本号方案不是将内部版本号 (major.minor.maint.build) 限制在 0-255 的范围内,还是这是一个过时的限制?我喜欢内部版本号的自动调整,但我希望这个数字很快就会变得相当高。
  • 好吧,我曾经有过生成.c.cpp.m 和相关.h 文件的此脚本版本,但如果我正在编写一个可可应用程序,我不会对此感到烦恼,只需在运行时从Info.plist 文件中读取它并将其存储在全局范围内。感谢git here 是原始版本,但请注意它的工作方式与我的答案略有不同,并且可能包含错误。
  • @SMGreenfield 我不知道版本号的限制。我总是使用major.minor 作为版本号和一个简单的整数作为内部版本号,从来没有遇到过问题。有趣的是这个数字有多高,但努力使它有意义,因为它不会增加每个构建,只是当源文件发生变化时,每个构建。
  • 再次感谢,令人印象深刻(您的许多帖子也是如此!)。明天我醒来时会更仔细地查看您的代码。仍然很好奇其他人是否遇到了 clang 缺陷。
【解决方案2】:

首先,我想澄清一下每个键的作用:

  • CFBundleShortVersionString

    描述应用发布版本的字符串,使用semantic versioning。此字符串将显示在 App Store 描述中。

  • CFBundleVersion

    指定构建版本(已发布或未发布)的字符串。它是一个字符串,但 Apple 建议改用数字。

  • CFBundleGetInfoString

    似乎已被弃用,因为它不再列在 Information Property List Key Reference 中。

在开发过程中,CFBundleShortVersionString 不会经常更改,我通常在 Xcode 中手动设置CFBundleShortVersionString。我定期更改的唯一字符串是CFBundleVersion,因为如果CFBundleVersion 没有更改,您将无法向iTunes Connect/TestFlight 提交新版本。

要更改值,我使用带有PlistBuddy 的 Rake 任务将时间戳(年、月、日、小时和分钟)写入CFBundleVersion

desc "Bump bundle version"
task :bump_bundle_version do
  bundle_version = Time.now.strftime "%Y%m%d%H%M"
  sh %Q{/usr/libexec/PlistBuddy -c "Set CFBundleVersion #{bundle_version}" "DemoApp/DemoApp-Info.plist"}
end

如果您还需要自动化 CFBundleShortVersionString,您可以使用 PlistBuddy。

【讨论】:

  • 据我所知,CFBundleGetInfoString 绝对仍在使用——因为我将它设置为包含我的公司名称和版权信息,而这正是 Get Info 的 Version 字段中显示的内容窗口。
  • 弃用并不意味着你不能使用它。只是不再推荐 :) 由于不再记录 CFBundleGetInfoString,我会寻找替代方案(例如 NSHumanReadableCopyright )。
猜你喜欢
  • 2011-07-03
  • 2020-08-19
  • 1970-01-01
  • 1970-01-01
  • 2011-12-30
  • 1970-01-01
  • 2021-04-06
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多