【问题标题】:Switch between source code and precompiled binaries在源代码和预编译的二进制文件之间切换
【发布时间】:2020-04-27 21:45:18
【问题描述】:

我们的应用程序中有大量的库。库是用 c++ 或 c# 编写的。 (平台:.net 框架,windows,64 位)将所有内容编译为源代码需要大量时间。我们正在考虑切换到预构建的二进制文件,但我们仍然希望保留返回源代码的可能性。作为我们使用 git 的版本控制系统,构建可以由 azure devops 完成。可以根据需要设置任何自定义服务器。

有哪些现成的包管理工具,以及在源代码和预构建二进制文件之间轻松切换的可能性? (如果工具与两种编程语言不兼容——可以只为一种语言指定工具集。)如果不存在这样的工具,你会自己推荐什么——使用什么样的包装,写什么样的脚本最重要的是?

是否可以识别 Api/abi 中断?

【问题讨论】:

  • 我真的不明白这个问题。你能试着用另一种方式问吗?谢谢。
  • 首先,C++ 和 C# 是非常不同的语言,编译和打包的机制也大不相同。它们在“API/ABI” 的含义上也存在根本差异。其次,您能否尝试更明确地解释您正在寻找的东西,也许可以展示您拥有什么以及您想要什么。您似乎在问“想知道如何执行源代码/二进制切换”。在这两种语言中,源代码通过编译切换二进制。我敢肯定你问的是不同的东西,但这很难说
  • 只是猜测,想问如何使用源代码管理?如git、github ..等。所以您将拥有一个其他人可以贡献的源代码控制,并且您可以控制源代码和编译版本?
  • 二进制依赖于操作系统和平台(处理器)。 ARM 11 处理器的二进制文件无法在使用 Intel x86 处理器的平台上运行。同样,为 ARM 11 平台上的 Linux 系统编译的二进制文件不会在 ARM 11 平台上的 Windows 10 系统上运行。发布源代码背后的想法是,人们可以为他们的平台编译它,否则您需要为操作系统和处理器的每个组合或排列发布库。
  • 此问题要求对工具进行推荐,这是关闭问题的正当理由之一。

标签: c# c++ nuget package-managers


【解决方案1】:

有哪些现成的工具可用于包管理,以及 是否可以在源代码和预构建的二进制文件之间轻松切换?

在 Visual Studio 中,您可以添加对另一个项目 (source code) 或另一个库 (dll) 的引用,但您 cannot add a reference to a project from a different solution

如果你想在 sourcedll 之间切换,那么你必须不断地从你的解决方案中添加/删除项目......这不是一个好的软件架构...这表明这些组件是紧密耦合,并且您需要一个组件的源代码才能测试另一个组件。软件组件应该相互独立。


将所有内容编译为源代码需要大量时间。我们曾经 考虑切换到预构建的二进制文件,但我们仍然希望 留下返回源代码的可能性

听起来您有一个大型应用程序,并且您希望将自己代码的一部分作为预编译组件引用,以减少构建时间。换句话说,您想建立自己的内部库。现在,外部库并没有什么特别之处。您可以轻松地 create a nuget packages 为您自己的任何类库

例如,您可以有 2 个解决方案:

您的Solution-1 包含您的类库项目。您可以为此解决方案中的每个项目创建一个 nuget 包。 (注意,nuget 包是.nupkg file

您的Solution-2 有您剩余的项目,您可以在此处安装上述软件包。

如果您需要更改库,可以在Solution-1 中修改它并创建一个新版本...当然您需要在Solution-2 中更新包。

注意:您不需要有 2 个单独的解决方案来创建 Nuget 包...您可以为任何类库构建一个 nuget 包

发布包

如果您希望每个人都可以访问您的包,那么您需要Publish 它,否则您可以将您的包放在组织内可访问的共享驱动器上。

重大变化

假设您已经制作了自己的 nuget 包:my-inhouse-lib,并且您想对其进行重大更改:

  1. 打开 my-inhouse-lib 源代码,进行更改,构建并创建一个新的 nuget 包,这将是:my-inhouse-lib-ver-2

  2. 你的其他项目依赖这个包,在这个项目上安装my-inhouse-lib-ver-2(这是你的开发环境)

  3. 由于新的重大更改,您的开发环境将中断...修复开发环境

  4. 将您的固定代码发布到测试/生产环境中...

这没什么特别的...就像更新任何其他 nuget 包一样

【讨论】:

  • nuget 打包是否也适用于 C++ 代码? nuget 包文件生成是否可以自动化,例如,如果我们将源代码作为单独的 git,可以使用 sparse git 从该 git 获取 nuget 包规范吗?
  • 如何使用 nuget 打包处理 api 中断?例如。您破坏了 API(需要重新编译)或 ABI(无需编译即可正常工作)-您如何从 nuget 打包这样的中断来配置它?
  • 创建nuget包与您的编程语言无关,只要您可以在Visual Studio中打开您的应用程序并构建它,您就可以创建一个nuget包(请查看链接中的说明)
  • 我并没有真正关注您的问题...生成 nuget 包与您的源代码管理和 git 无关...您需要构建项目并使用 Command 中的 nuget pack 命令提示获取.nuget 文件...然后将其安装在您需要该软件包的位置...就像安装任何其他库一样。
  • 也可以为 C++ 使用 nuget 打包,请参阅此链接:digitalhouseblog.wordpress.com/2019/08/22/… 但实际上这并不支持 API / ABI 选择。
【解决方案2】:

我读到您的问题是说依赖项位于 .Net 或 C++ 中;并且您的最终输出版本是 .Net。您知道 NuGet 是所有与依赖相关的东西的“标准”.Net 工具。

现在。这取决于您想要在源代码和二进制文件之间轻松“切换”的想法究竟是什么?

你不区分构建服务器上的开发者机器上的。您当然希望两者都从二进制文件构建以尽可能快,但我无法理解在构建服务器上需要源代码:源代码是为人类而不是机器提供的。

选项1。你的意思是'获得二进制的构建速度但获得源代码的调试经验'

您可以使用 NuGet 实现此目的。您正在使用 Azure,因此您可以将任务添加到您的库构建以直接发布到私有 Azure NuGet 源。你的出发点是

哪个能说服你

  1. 设置供稿
  2. 发布到供稿
  3. 消耗饲料

Nuget 可以为您提供完整的“调试时源代码”体验,因为 Nuget 发布可以包含源符号。这在锚点的同一页面上进行了介绍,

在开发人员计算机上,将解决方案中项目的依赖项替换为 NuGet 包的依赖项。然后,您将获得以下正常的 nuget 体验:

  • NuGet 缓存从您计算机上的源中编译的 .dll
  • NuGet 会在有可用更新时通知您
  • 您可以选择何时获取最新信息;或者您可以强制执行 always-update-on-build ;或者您可以强制执行结帐时始终更新。

Force update-on-checkout 将为您提供源代码风格的体验,即始终查看(几乎)最新的代码,但仍然可以以最快的速度从二进制构建。你可以通过添加一个 git post-checkout 钩子来强制 update-on-git-checkout 运行nuget update,这样每个 git get latest 都会同时从 nuget 获取最新的。 https://ddg.gg/example%20git%20oncheckout%20hook%20windows.

( 这可能有助于在开发人员机器上强制执行库的标准检出路径,因为 .Net 符号文件将包含上次构建使用的路径。但是,这需要一些努力和 windows 知识如何在开发人员上重现路径构建服务器使用的机器。)

选项 2。您的意思是“能够在从源代码构建和从二进制构建之间轻松切换”

没有开箱即用的解决方案,因为这是一个不寻常的要求。 “简单”的方法是拥有单独的项目和解决方案。相反,再次问究竟你想达到什么目标?

如果您只是想确保从最新的源构建,那么选项 1 已经解决了这个问题 - 给或需要几秒钟 - 对于构建服务器和开发机器,因为您的 Azure nuget 提要将始终包含非常最新的版本。

( 实际上,如果您使用自动化测试,选项 1比从源代码构建更好,因为它使用“构建并通过测试的最新源”而不是“某人最近的东西不小心签到了。)

如果您想要“我的项目中的完整源代码但快速构建”,这可以自动发生:msbuildcmake 一样,如果项目已经是最新的,则不会重新构建项目。但是,Visual Studio 开发人员习惯性地记住重建(= clean + build)而不是 build 的击键。如果您有大量依赖项,那么学习更改这一击键可以节省您所有的等待时间。问题是,如果其他开发人员正在进行大量更改,则无法避免获取最新源和重建时间。

你可以有不同的项目文件来构建开发机器和构建服务器。维护这将是一个容易出错的拖累,因此您应该脚本从标准文件自动生成构建服务器 csproj 文件。然后构建服务器可以从 nuget 获取依赖项,而开发机器使用源 并且 还将成功构建的库的更新推送到 nuget 提要。

评论:我们离两全其美的距离有多近?

无法避免这样一个事实,即具有许多依赖项的大型项目会带来小型项目所没有的成本。 或者您要为大型项目付出代价,这些项目的打开速度和构建速度都很慢; 分解构建(通过 nuget 或其他方式)并失去对所有源代码的即时简单访问。

带有符号的 NuGet 提要提供了最主流、最简单的解决方案。

【讨论】:

    【解决方案3】:

    我们正在考虑切换到预构建的二进制文件,但我们仍然希望保留返回源代码的可能性

    对于 C++,您可以使用 Conan(我相信还有其他类似的工具,但我没有使用过)。您可以将预构建的包上传到服务器,并让您的开发人员在需要时安装它们。这就像将依赖项添加到您作为项目的一部分保留的 conanfile.py 的需求列表中一样简单。要从源代码构建(例如,当您的服务器缺少二进制文件时),您可以在 conan install 命令(安装项目依赖项)中添加 --build 选项。

    在源代码和预构建的二进制文件之间轻松切换

    您是否尝试在项目的源代码树中保留依赖项而不是与二进制文件链接之间切换?如果您使用包管理器,我看不到将它放在源代码树中的任何长期好处。当然,如果依赖项的源代码是源代码树的一部分(例如不必在目录之间切换),那么修改依赖项的源代码可能会稍微方便一些,但我认为从长远来看它可能更有条理和更高效运行以在其自己的项目中单独更改依赖项。 (在柯南中你可以使用“用户”和“频道”来管理这些自定义版本的第三方库)

    是否可以识别 Api/abi 中断

    在我看来,管理此问题的最佳方法是跟踪您的依赖版本。同样,像柯南这样的包管理器可以帮助您解决这个问题,因为您可以在需求中指定依赖项的版本。

    使用 Conan 的一个好处是您还可以添加构建依赖项来引入您的环境。例如,CMake 可以是构建依赖项。因此,您的包将首先安装指定的 CMake 版本,然后安装使用 CMake 构建的其余依赖项。这使您的 DevOps 管理变得更加容易。

    【讨论】:

    • 我曾尝试在 Windows 上安装柯南,但没有任何运气。你自己在 windows 上试过柯南吗?
    • 是的,我有。你只需要pip install conan。(我推荐创建一个Python virtualenv)
    【解决方案4】:

    对于 C++ 项目,您可以根据需要检查 CMake。您可以保留不同的构建选项,例如 1) 构建整个源代码 2) 将子模块构建到库中,然后将其重用于主项目。最新的 CMake 也支持预编译的头文件。您也可以检查它以减少编译时间。

    Conanvcpkg 是可用于 C++ 模块的两个包管理器

    【讨论】:

    • 我曾尝试在 Windows 上安装柯南,但没有任何运气。你自己在 windows 上试过柯南吗?
    • 需要注意的是,柯南还不支持在 Microsoft 符号服务器上发布调试符号 - github.com/conan-io/conan/issues/4047 。我没有要求支持那个,但这也可能是决策者。
    • @TarmoPikaro 我没怎么探索柯南。
    【解决方案5】:

    我们正在考虑切换到预构建的二进制文件 但我们仍然希望保留返回源代码的可能性

    首先您需要一个构建系统,我建议您使用cmake 来处理您的源代码 代码及其依赖项。 让我们在这里进行测试设置,并假设您的工作目录如下所示:

    project_folder
    |-- lib
    |   |-- LibA.cpp
    |   |-- LibA.hpp
    |-- tools
    |   |-- conanfile.py
    |   |-- conanfile.txt
    |-- CMakeLists.txt
    |-- main.cpp  
    

    它们应该是什么:CMakeLists.txt 是你的 cmake 配置文件,要处理

    是否可以识别 Api/abi 中断?

    conanfile.py 是你的配方,意味着你需要打包创建你的预建库,conanfile.txt 是你的需要文件使用您预先构建的库。

    显然,我还建议使用conan 来处理您的预构建库,或者也称为packages

    现在让我们看一下我的示例以及如何使用它: CMakeLists.txt

    cmake_minimum_required(VERSION 3.15)
    
    project(my_project LANGUAGES CXX)
    
    add_library(LibA lib/LibA.cpp)
    
    add_executable(ExecutableA main.cpp)
    target_link_libraries(ExecutableA LibA)
    
    target_include_directories(LibA PUBLIC ${CMAKE_CURRENT_LIST_DIR}/lib)
    

    在我的示例中我会坚持使用cpp,但对于您的.net 框架,您可以检查thisthis

    所以我只是在那个文件中定义了我的targets,并声明了它们之间的依赖关系,内容如下: main.cpp:

    #include <iostream>
    #include "LibA.hpp"
    
    
    int main()
    {
      printTesting();
      std::cout << "Hello World! again" << std::endl;
      return 0;
    }
    

    LibA.cpp

    #include <iostream>
    #include "LibA.hpp"
    
    void printTesting(void)
    {
      std::cout << "Testing Something" << std::endl;
    }
    

    LibA.hpp

    #include <iostream>
    
    void printTesting(void);
    

    我只是假设,您不熟悉 cmake:我们会让 cmake 为我们生成 Makefile,或者为您生成 evtl。 .vcxproj 如果您使用 .net 生成器,但我想在这里保持简单,所以我们将配置项目并构建二进制文件。

    mkdir ../build && cd ../build
    cmake ../project_folder && make
    

    你会得到你的 LibAExecutableA ,所以这里还没有什么特别的,只是 cmake 现在照顾你的依赖关系和 evtl。如果发生变化(例如,您的项目中的新 commits),请重新构建它们。 conanfile.py 像这样:

    from conans import ConanFile, tools
    
    
        class LibAConan(ConanFile):
            name = "libA"
            version = "0.1"
            settings = "os", "compiler", "build_type", "arch"
            description = "<Description of LibA here>"
            url = "None"
            license = "None"
            author = "None"
            topics = None
    
            def package(self):
                self.copy("*.a")
    
            def package_info(self):
                self.cpp_info.libs = tools.collect_libs(self)
    

    你想:

    cp ../project_folder/tools/conanfile.py && conan export-pkg . libA/0.1@myuser/testing
    

    我们的包名是:libA in 0.1 版本。 myuser/testing 是频道,如果您对多个目标平台进行交叉编译,这很有趣。

    我们通过调用conan export-pkg创建了可重用包,并缓存在 $HOME_DIR/.conan/data/libA/0.1/myuser/testing(或在您的环境中检查:conan config home

    事件。你想先安装柯南,所以检查这个installation guideline或这个downloads和你的特殊设置:conan with azure devops

    所以这些创建的包也可以上传到任何remote_server$ conan upload libA/0.1@myuser/testing --all -r=my_remote_server(类比:就像在 git 中提交后的 git push)。

    所以我们有 configuredbuiltcmakecreateduploaded 使用 conan 的包,您的同事可以重用预构建的包/二进制文件通过在 pythonfile.txt 中定义他们的要求:

    [requires]
    libA/0.1@myuser/testing
    
    [generators]
    cmake
    

    他们可以这样做:

    mkdir ../build_using_prebuild && cd ../build_using_prebuild && cp ../project_folder/tools/conanfile.txt . && conan install -g deploy . -r my_remote_server
    

    conan 将搜索所需的包和 evlt。从 my_remote_server 下载它

    如果他们现在运行cmake ../project_folder &amp;&amp; make,将使用那些预构建的包,而不是从头开始编译src

    当然,我建议您在 azure 中自动执行这些步骤,但我想您已经了解了如何使用 构建系统包管理器 来协调您的构建.

    【讨论】:

      【解决方案6】:

      这个问题有多个轴 - 源代码构建、软件包安装、外部存储库、api/abi 中断、构建系统本身。

      首先是您的要求 - 您是否只对打包和/或安装感兴趣。

      对于包装本身,可以使用以下包装系统: nuget, conan, vcpkg, choco.

      很可能您不仅会对发布包本身感兴趣,还会对它们的调试符号感兴趣。

      对于 azure devops / debug 符号发布文档可以从以下链接中找到,例如:

      https://azure.microsoft.com/en-us/blog/deep-dive-into-azure-artifacts/ https://docs.microsoft.com/en-us/nuget/create-packages/symbol-packages-snupkg

      柯南(c++封装)目前不支持符号发布: https://github.com/conan-io/conan/issues/4047

      要获得源代码分发,可以使用 git 本身。 Git 子模块确实允许创建外部 git 存储库引用,但是一旦添加了外部子模块,您需要使用双重提交来更改所有内容 - 一次 git 提交到子存储库,一次 git 提交更新主存储库中引用的 git 哈希。

      确实存在一些解决此问题的方法,例如:没有子模块的嵌套 git:

      Nested git repositories without submodules?

      =>

      http://debuggable.com/posts/git-fake-submodules:4b563ee4-f3cc-4061-967e-0e48cbdd56cb

      如果您使用预构建的二进制文件而不是源代码构建主存储库 - 在理想的解决方案中,您不需要外部 git 存储库,这也意味着不需要 git clone / checkout 外部存储库。

      那么也可以有all-in-one solution 或多个解决方案,每个单独编译。

      确实更容易拥有all-in-one solution,因为编译依赖解决方案的麻烦更少。

      要实现完全动态的解决方案生成,例如可以使用 cmake - 它可以根据 cmake 使用的预配置选项生成解决方案。 (见 cmake 的"option")。

      cmake 对于 C++ 来说确实是一个不错的选择(支持预编译头文件,加快统一构建速度),但对于 C# 来说可能不是那么简单(很难找到必要的参数来正确配置它)。

      目前(5.2020)还没有找到比 cmake 更好的替代品,但有多种工具和生态系统与 cmake 并行发展,因此需要观察未来可能会带来什么。

      然后关于包装。 choco 似乎是 nuget 的一个子集 - 它使用自己的支持软件更新的 xml 标签扩展了 nuspec。 (参考网页:https://chocolatey.org/https://docs.microsoft.com/en-us/nuget/reference/nuspec

      如果您想发布 nuget 包甚至是安装包,可以使用 nuget 存储库(无需任何额外费用)。

      如果不足以满足您的需求,也可以将 nuget 服务器升级为 choco 服务器,但可能会花费一些 - 请参阅巧克力网页。

      在任何级别都无法观察到 Api/abi 中断,直到实际发生编译/链接错误或应用程序在运行时崩溃 - 唯一可以做的替代方法是控制打包本身的版本控制 - 所以主包 nuget(或choco) 版本会 需要更高版本的依赖 nuget(或 choco)版本。

      如果主存储库是由与子存储库相同的人开发的——那么 api/abi 中断可以由开发人员自己完成,但如果两个 git 彼此独立并由不同的团队开发——那么 api/abi 中断可能发生在任何时间点。

      确保存储库一致的最佳方法 - 对主存储库进行单元测试,这将检查没有发生 api/abi 中断。

      理论上,如果发生 api/abi 中断 - 构建系统本身可以继续使用两种构建替代方案 -

      1. 使用最后一个工作子存储库跟随主存储库
      2. 关注子存储库而不构建主存储库

      可以通过使用分支来应用相同的机制,例如

      1. “主 git:主分支”+“子 git:发布/1.0 分支”
      2. “子 git:主分支”

      这是一种机制,可能不仅需要观察两个存储库,还需要在长期失败的情况下切换分支。 (例如,子 git 的开发团队不关心主 git 中发生的构建中断)

      怀疑不存在用于此目的的任何现成工具(如果出现,请留下评论),因为这可能需要重新连接来自可能不同 git 存储库和可能不同组织的两个可能不同构建链的工具。

      最后但同样重要的是 - 如果您希望您的应用程序能够进行软件更新 - 那么 nuget/choco 分发下载可以与额外的 nuget/choco 服务器一起使用。

      这里是 nuget 包下载部分的好例子: https://github.com/mwrock/NugetDownloadFeed

      安装包构建有点复杂 - 请参阅以下链接:

      (根据我的意见,按最坏的顺序排列)

      【讨论】:

        【解决方案7】:

        您可以在 Visual Studio 中使用引用路径。我在 Asp.Net Web Api 项目中使用它并为我工作但我在 Asp.Net MVC(不是 Api)中有问题

        当您加载项目时,Visual Studio 会忽略引用路径,当您卸载项目时,Visual Studio 会使用预编译的引用,并且可以在它们之间切换。

        【讨论】:

          猜你喜欢
          • 2020-10-22
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-06-08
          • 1970-01-01
          • 2014-03-01
          • 1970-01-01
          相关资源
          最近更新 更多