How to Refactor Monolithic Repositories for Embedded “Bare Metal” Applications and Shared Libraries with Inter-Dependencies?
I work on an embedded software team responsible for multiple in-house developed devices. Each device runs several “bare metal” applications—there is no operating system like Linux; instead, binary images are flashed to the devices, with each image statically linking all required code. Only one application can run at a given time. Current Setup and Issues Monolithic Repository per Device Each device has a single repository that contains: Code for all the device’s applications. Static libraries providing common features (e.g., networking, cryptography) that are shared between the applications. Shared Code Challenges Breaking Changes: When one developer makes a change in a library shared between applications to add a new feature, the update is committed directly to the repository’s main branch. While the change may work for the developer’s specific application, it can inadvertently break up other applications that depend on the same library. Duplicated Fixes: A bug fix in a library in one device repository must then be manually replicated across other device repositories, increasing maintenance overhead. Proposed New Approach Separate Repositories: Move each library and each application to its own dedicated Git repository. Version Control for Dependencies: In each application repository, specify the exact version (using tags or commit hashes) for each required library. This is similar to dependency management strategies (such as Zephyr’s west) where an application “pulls” the precise version of each library it depends on. Advantages Isolation of Changes: A breaking change in a library will not force an immediate upgrade on all applications—each application remains pinned to the version that has been thoroughly tested. Streamlined Bug Fixes: A reported bug can be addressed by updating the pinned version for a single application, enabling the QA team to validate the change independently. This provides stability while development on the common libraries continues. Reduced Duplication: Fixes in common libraries do not need to be manually replicated across multiple device repositories. New Requirement – Inter-Dependent Libraries The challenge grows when we introduce inter-dependencies among libraries. For example, a “Hardware Abstraction Layer” (HAL) library is utilized by almost every other library. How should we effectively manage versioning and dependency control when libraries themselves depend on other libraries? Specifically: What strategies or tools can resolve conflicts or manage cascading changes across a hierarchy of inter-dependent libraries? Are there approaches from similar embedded or constrained environments that may better serve our needs in terms of modularity, testing, and maintenance? Additional Context and Considerations Embedded Development Constraints: Since our applications are “bare metal,” all dependency resolutions must be handled at build time with no runtime dependency management. Impact on Workflow: How do we strike a balance between the stability required for intensive QA testing (pinning tested versions) and the flexibility needed to integrate improvements or necessary fixes in common libraries, especially as those libraries are used across multiple applications? Request for Community Input I’m looking for guidance on: Best practices for managing versioned dependencies in a modularized codebase for embedded “bare metal” systems. Tools, methodologies, or dependency management systems that have been effective in similar scenarios with inter-library dependencies. Potential pitfalls or alternative strategies that might be considered, given the dual priorities of ongoing development and ensuring the stability needed for QA testing and bug resolution. Any insights, experiences, or references to similar cases would be greatly appreciated!

I work on an embedded software team responsible for multiple in-house developed devices. Each device runs several “bare metal” applications—there is no operating system like Linux; instead, binary images are flashed to the devices, with each image statically linking all required code. Only one application can run at a given time.
Current Setup and Issues
Monolithic Repository per Device
Each device has a single repository that contains:
- Code for all the device’s applications.
- Static libraries providing common features (e.g., networking, cryptography) that are shared between the applications.
Shared Code Challenges
Breaking Changes:
When one developer makes a change in a library shared between applications to add a new feature, the update is committed directly to the repository’s main branch. While the change may work for the developer’s specific application, it can inadvertently break up other applications that depend on the same library.Duplicated Fixes:
A bug fix in a library in one device repository must then be manually replicated across other device repositories, increasing maintenance overhead.
Proposed New Approach
Separate Repositories:
Move each library and each application to its own dedicated Git repository.Version Control for Dependencies:
In each application repository, specify the exact version (using tags or commit hashes) for each required library. This is similar to dependency management strategies (such as Zephyr’s west) where an application “pulls” the precise version of each library it depends on.
Advantages
Isolation of Changes:
A breaking change in a library will not force an immediate upgrade on all applications—each application remains pinned to the version that has been thoroughly tested.Streamlined Bug Fixes:
A reported bug can be addressed by updating the pinned version for a single application, enabling the QA team to validate the change independently. This provides stability while development on the common libraries continues.Reduced Duplication:
Fixes in common libraries do not need to be manually replicated across multiple device repositories.
New Requirement – Inter-Dependent Libraries
The challenge grows when we introduce inter-dependencies among libraries. For example, a “Hardware Abstraction Layer” (HAL) library is utilized by almost every other library.
How should we effectively manage versioning and dependency control when libraries themselves depend on other libraries? Specifically:
- What strategies or tools can resolve conflicts or manage cascading changes across a hierarchy of inter-dependent libraries?
- Are there approaches from similar embedded or constrained environments that may better serve our needs in terms of modularity, testing, and maintenance?
Additional Context and Considerations
Embedded Development Constraints:
Since our applications are “bare metal,” all dependency resolutions must be handled at build time with no runtime dependency management.Impact on Workflow:
How do we strike a balance between the stability required for intensive QA testing (pinning tested versions) and the flexibility needed to integrate improvements or necessary fixes in common libraries, especially as those libraries are used across multiple applications?
Request for Community Input
I’m looking for guidance on:
- Best practices for managing versioned dependencies in a modularized codebase for embedded “bare metal” systems.
- Tools, methodologies, or dependency management systems that have been effective in similar scenarios with inter-library dependencies.
- Potential pitfalls or alternative strategies that might be considered, given the dual priorities of ongoing development and ensuring the stability needed for QA testing and bug resolution.
Any insights, experiences, or references to similar cases would be greatly appreciated!