在源代码和预编译的二进制文件之间切换

Switch between source code and precompiled binaries

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

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

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

What kind ready made tools there exists for package management, and possibility to easy switch between source code and pre-built binaries?

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

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


Compiling everything as source code takes a lot of time. We were thinking about switching to prebuilt binaries, but we still would like to leave a possibility to return back to source code

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

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

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

你的Solution-2有你剩余的项目,你可以在这里安装上面的包。

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

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

发布包

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

重大变更

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

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

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

  3. 你的开发环境会因为新的重大变化而崩溃...修复开发环境

  4. 将您的固定代码发布到 Test/Prod 环境中...

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

We were thinking about switching to prebuilt binaries, but we still would like to leave a possibility to return back to source code

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

to easy switch between source code and pre-built binaries

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

Is it possible to identify Api/abi breaks

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

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

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

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

我读到你的问题是说依赖项在 .Net 或 C++ 中;并且您的最终输出版本是 .Net。您知道 NuGet 是 'the' 标准 .Net 工具,适用于所有相关依赖项。

现在。这取决于您 确切地 想要什么 'switching' 源代码和二进制文件之间的简单想法?

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

选项1.你的意思是'get the build speed of binary but the debugging experience of source'

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

它告诉你通过

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

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

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

  • NuGet 缓存来自您计算机上的提要的已编译 .dll
  • NuGet 会在更新可用时通知您
  • 您选择何时获取最新;或者你可以强制执行 always-update-on-build ;或者您可以强制执行结帐时始终更新。

结帐时强制更新将为您提供源代码风格的体验,让您始终查看(几乎)最新的代码,但仍然可以最快地从二进制文件构建。您可以通过向 运行 nuget update 添加 git post-checkout 挂钩来强制更新-git-checkout,这样每个 git获取最新信息将同时从 nuget 获取最新信息。 https://ddg.gg/example%20git%20oncheckout%20hook%20windows.

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

选项2,你的意思是'be able to easily switch between building from source and building from binary'

没有开箱即用的解决方案,因为这是一个不寻常的要求。 'simple' 方法是有单独的项目和解决方案。相反,请再次询问 您究竟 想要达到什么目的?

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

( 事实上,如果您使用自动化测试,选项 1 比从源代码构建更好,因为它使用 'most recent source that builds and passes tests' 而不是 'most recent thing someone accidentally checked in' .)

如果您想要 'full source in my project but fast builds',这会自动发生:msbuildcmake 一样,如果项目已经是最新的,则不会重建项目。但是,visual studio 开发人员习惯性地记住重建 (= clean + build ) 而不是构建的按键。如果你有大量的依赖项,学习改变这个按键可以节省你所有的等待时间。问题是,如果其他开发人员正在进行大量更改,则无法避免获取最新源代码和重建时间。

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

评论:我们能达到两全其美的程度有多近?

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

NuGet feeds with symbols 提供了最主流、最简单的尝试来解决这个问题。

We were thinking about switching to prebuilt binaries but we still would like to leave a possibility to return back to source code

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

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

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

Is it possible to identify Api/abi breaks ?

and conanfile.py 是你的配方,意味着你需要 package and create 你的预建库和 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 框架,您可以检查 this or this

所以我只是在那个文件中定义了我的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

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

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

Evtl。你想先安装柯南,所以检查这个 installation guideline or this downloads and for your special setup: conan with azure devops

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

所以我们有 configuredbuiltcmake 以及 createduploaded 包使用 conan,您的同事可以通过在 pythonfile.txt 中定义他们的要求来重用预构建的 packages/binaries:

[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 && make,将使用那些预构建的包,而不是从头开始编译 src

当然,我建议您在 azure 中自动执行这些步骤,但我想您已经明白 构建系统 程序包管理器 可用于编排您的构建。

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

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

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

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

对于 azure devops / 调试符号发布文档,可以从以下 link 中找到,例如:

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 克隆/检出外部需要存储库。

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

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

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

cmake 对于 C++ 来说确实是一个很好的选择(支持预编译头文件,unity building 加速),但对于 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 在任何级别上都无法观察到中断,直到实际编译/link 错误发生或应用程序在 运行 时崩溃 - 唯一可能的选择 - 是控制包装本身的版本控制 - 所以主包 nuget(或 choco)版本会 需要更高版本的依赖 nuget(或 choco)版本。

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

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

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

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

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

  1. "main git: master branch" + "child git: release/1.0 branch"
  2. "child git: master branch"

这是一种机制,可能不仅需要观察两个存储库,还需要切换分支以防出现长期故障。 (例如,子 git 的开发团队不关心主 git 中发生的构建中断)

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

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

下面是 nuget 包下载部分的一个很好的例子: https://github.com/mwrock/NugetDownloadFeed

安装包构建有点复杂 - 请参见下面的示例 links:

(根据我的意见,按最佳-最差顺序排列)

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

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