跟踪源代码变体
我很快就开始维护一系列包含相同嵌入式软件变体的产品。由于我已经使用 git 一年了并且非常欣赏它,所以我很可能使用它来进行源代码控制。
我可以看到多种用于维护固件变体的选项,但没有一个让我太满意。您在自己的工作中应用了哪些最佳实践?
我能想到的替代方案:
定义。预处理。 优点:所有内容都始终存在于源代码中,很难错过其中一款产品的更新。 缺点:较难阅读。当我们只有两个变体时可能还好,当它变成四个或更多时,那就很痛苦了。此外,应用 DRY 原则(不要重复自己)似乎更困难。
每个产品变体一个分支。 当包含适用于所有产品的更改时,必须将更改合并到其他产品。 缺点:如果提交包含所有产品的更改和特定变体的更改,则会出现问题。当然,您可以确保提交仅包含一种更改:此产品更改或整个系列更改。但尝试将其强加给团队?另外,合并是行不通的,我们应该精挑细选。对吗?
作为子模块的核心存储库。 将包含核心功能的所有文件单独作为存储库。 所有产品都包含核心存储库的一个版本作为子模块。 缺点:我看不出最终不会有核心子模块的变体。然后我们又遇到麻烦了,然后我们又会使用定义或一些不好的东西。 有分支的核心存储库?然后我们回到之前的选择:必须合并适用于所有分支的更改,但合并还包括产品特定的内容。
为每个模块创建一个存储库。 例如,显示驱动程序的存储库、电源管理硬件的另一个存储库、用户输入接口的另一个存储库…… 优点:良好的模块化。只需选择您需要的模块作为子模块即可制作新产品!所有子模块都可能有分支,例如一个变体以不同的方式使用硬件。 缺点:有很多很多模块,每个模块都跟踪几个文件(一个包含文件和一个源文件)。一个麻烦。 有人在某些模块中进行了重要更新?然后,如果合适的话,需要有人将更改包含在该模块的其他分支中。然后有人还必须更新每个产品存储库中的子模块。 做了相当多的工作,我们有点失去了 git 的快照部分。
你是如何做到的,效果如何?或者你会怎么做?
我有一种感觉,我应该体验一下樱桃采摘。
I am soon starting to maintain a line of products containing variants of the same embedded software. Since I've been playing with git for one year and appreciate it very much, I'm likely to use it for source control.
There are several options I can see for maintaining the variants of the firmware, but none pleases me too much. What are the best practices you apply for your own work?
Alternatives I can think of:
defines. Pre-processing.
Pros: everything is always present in the source code, it is harder to miss the update of one of the products.
Cons: harder to read. It might be ok while we have only two variants, when it becomes four or more it will be a pain. Also, it seems harder to apply the DRY principle (Don't Repeat Yourself).one branch per product variant.
When changes that apply to all products are included, the change must be merged to the other products.
Cons: if a commit contains both changes for all products and changes for the specific variant, there will be trouble. Of course, you can make sure that a commit contains only one kind of change: this-product-changes, or the-whole-family-changes. But try to force that on a team?? Plus then merging wouldn't work, we should be cherry-picking instead. Right?a core repository as a submodule.
Make all files that contain core functionality a repository on its own.
All products contain a version of the core repository as a sub-module.
Cons: I can't see that there wouldn't eventually be variants of the core submodule. Then we're in trouble again, and then we'd use defines or something bad again.
A core repository with branches? Then we're back to the previous alternative: a change that applies to all branches must be merged but merging includes also the product specific stuff.create a repository per module.
For example a repository for a display driver, another one for the power management hardware, yet another for the user input interface, ...
Pros: good modularity. Make a new product by just picking up the modules you need as submodules! All submodules might have branches, if for example a variant uses the hardware in a different way.
Cons: Lots and lots of modules, each keeping track of a couple of files (an include file and a source file). A hassle.
Someone makes an important update in some module? Then someone needs to include the change in the other branches of this module, if appropriate. Then someone must also update the submodule in every product repository.
Quite some work, and we kindof lose the snapshot side of git.
How do you do it, and how has it been working? Or how would you do it?
I have a feeling that I should get experienced with cherry picking.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
我会尝试尽可能多地使用
#define
。使用正确的代码,您可以最大限度地减少对可读性和重复性的影响。但同时
#define
方法可以安全地与拆分和分支相结合,其应用取决于代码库的性质。I'd try to go for
#define
s as much as possible. With a proper code you can minimize the impact on readability and repetitions.But at the same time
#define
approach can safely be combined with splitting and branching, application of which depends on the nature of the codebase.我不确定这是否是“最佳实践”,但 Scintilla 项目使用了多年来仍然相当易于管理的东西。它只有一个所有平台通用的分支(主要是 Windows/GTK+/Mac,但也有 VMS、Fox 等的变体)。
不知何故,它使用了非常常见的第一个选项:它使用定义来管理源代码中的小平台特定部分,在这些部分中放置通用代码是不切实际或不可能的。
请注意,此选项对于某些语言(例如 Java)是不可能的。
但可移植性的主要机制是使用面向对象:它抽象一些操作(绘图、显示上下文菜单等)并为每个目标使用一个(或多个)平台文件,提供具体的实现。
makefile 仅编译正确的文件并使用链接来获取正确的代码。
I am not sure if that's "best practice", but the Scintilla project uses something for years that is still quite manageable. It has only one branch common to all platforms (mostly Windows/GTK+/Mac, but with variants for VMS, Fox, etc.).
Somehow, it uses the first option which is quite common: it uses defines to manage little platform specific portions inside the sources, where it is impractical or impossible to put common code.
Note that this option is not possible with some languages (eg. Java).
But the main mechanism for portability is using OO: it abstracts some operations (drawing, showing context menu, etc.) and uses a Platform file (or several) per target, providing the concrete implementation.
The makefile compiles only the proper file(s) and uses linking to get the proper code.
我认为适当的答案部分取决于变体的根本不同程度。
如果有小部分不同,则对单个源文件使用条件编译是合理的。如果变体实现仅在调用接口处一致,那么最好使用单独的文件。您可以通过条件编译将完全变体的实现包含在单个文件中;这有多混乱取决于变体代码的数量。如果是四个变体,每个变体大约 100 行,也许一个文件就可以了。如果是 100、300、500 和 900 行的四种变体,那么一个文件可能不是一个好主意。
您不一定需要单独分支上的变体;事实上,您应该只在必要时使用分支(但一定要在必要时使用它们!)。例如,您可以将四个文件全部放在一个公共分支上,并且始终可见。您可以安排编译以选择正确的变体。一种可能性(还有许多其他可能性)是编译一个源文件,该文件知道在给定当前编译环境的情况下要包含哪个源变体:
I think that the appropriate answer depends in part on how radically different the variants are.
If there are small portions that are different, using conditional compilation on a single source file is reasonable. If the variant implementations are only consistent at the call interface, then it may be better to use separate files. You can include radically variant implementations in a single file with conditional compilation; how messy that is depends on the volume of variant code. If it is, say, four variants of about 100 lines each, maybe one file is OK. If it is four variants of 100, 300, 500 and 900 lines, then one file is probably a bad idea.
You don't necessarily need the variants on separate branches; indeed, you should only use branches when necessary (but do use them when they are necessary!). You can have the four files, say, all on a common branch, always visible. You can arrange for the compilation to pick up the correct variant. One possibility (there are many others) is compiling a single source file that knows which source variant to include given the current compilation environment:
你将陷入一个充满伤害的世界!
无论你做什么,都需要一个自动构建环境。至少您需要某种自动方式来构建固件的所有不同版本。我遇到过修复一个版本中的错误并破坏另一版本的构建的问题。
理想情况下,您将能够加载不同的目标并运行一些冒烟测试。
如果您采用 #define 路线,我会将以下内容放在检查变体的地方:
这将确保在构建过程中为同一变体构建所有文件。
You are in for a world of hurt!
Whatever you do, you need an automatic build environment. At the very least you need some automatic way of building all the different versions of your firmware. I've had issues of fixing a bug in one version and breaking the build of a different version.
Ideally you would be able to load the different targets and run some smoke tests.
If you go the #define route, I would put the following someplace where the variant is checked:
This will make sure all the files are built for the same variant during the build process.
我面临着同样的问题。我不仅想在目标之间共享源代码,还想跨平台共享源代码(例如 Mono 与 Visual studio)。似乎没有任何方法可以仅使用 Git 提供的分支/标记版本来轻松做到这一点。因为理想情况下,您想要维护一个公共代码分支和主机/目标特定分支,然后将公共代码合并到这些分支中或从这些分支中合并出来。
但我不知道该怎么做。也许是分支和标记的组合?有人吗?
显然,如果需要的话,我会使用条件编译,这样版本控制下就只有一个源文件,但对于其他内容文件来说,这些文件应该只出现在一个主机/目标分支中,或者内容将出现在其中完全不同,就需要不同的机制。如果您可以使用面向对象技术来回避问题,那也是可取的。
所以答案似乎是你需要某种配置/构建管理机制
git 来管理这个。但这似乎会增加很多复杂性,所以也许要精挑细选
合并到一个公共分支并不是一个坏方法。特别是如果您使用其他技术将源变体保持在最低限度。 (标记可以帮助自动化执行此操作吗?)。
I'm facing the same issue. I want to share source not just among targets, but across platforms (e.g Mono vs. Visual studio. It seems like there isn't any wway to easily do that with just the version the branching/tagging provide by Git. Because ideally, you'd like to maintain a common code branch, and host/target specific branches and just merge the common code in and out of those branches.
But I don't know how to do that. Maybe a combination of branching and tagging? Anybody?
Obviously, I would use condition compilation if it was needed where I could, so that there would only be one source file under version control, but for the other stuff files that should only appear in one host/target branch, or where the contents would be completely different, a different mechanism is needed. If you can use OO techniques to side step the problem that too would be desirable.
So the answer seems to be you need some sort of configuration/build management mechanism on top
of git to manage this. But that would seem to add a lot of complexity, so maybe cherry picking
merges into a common branch wouldn't be a bad way to go. Especially if you use other techniques to keep the source variants to a minimum. (Could tagging help automate this?).
您应该尽可能将每个变体的自定义代码保存在其自己的文件集中。然后,您的构建系统(Makefile 或其他文件)根据您正在构建的变体选择要使用的源。
这样做的优点是,当处理特定变体时,您可以看到它的所有代码,而不会出现其他变体的代码来混淆事物。可读性也比在源代码中乱七八糟地使用 #ifdef、#elif、#endif 等要好得多。
当您知道将来需要合并分支中的所有代码时,分支效果最好进入主分支(或其他分支)。它不适用于仅合并分支之间的一些更改(尽管当然可以完成)。因此,为每个变体保留单独的分支可能不会产生好的结果。
如果您使用上述方法,则无需尝试在版本控制中使用此类技巧来支持代码的组织。
You should strive to, as much as possible, keep each variant's custom code in its own set of files. Then your build system (Makefile or whatever) selects which sources to use based on which variant you are building.
The advantage to this is that when working on a particular variant, you see all of its code together, without other variants' code in there to confuse things. Readability is also much better than littering the source with #ifdef, #elif, #endif, etc.
Branches work best when you know that in the future you will want to merge all of the code from the branch into the master branch (or other branches). It doesn't work as well for only merging some changes from branch to branch (although it can certainly be done). So keeping separate branches for each variant will probably not yield good results.
If you use the aforementioned approach, you don't need to try to use such tricks in your version control to support your code's organization.