Quản lý phần phụ thuộc

Khi xem qua các trang trước, một chủ đề lặp đi lặp lại: việc quản lý mã của riêng bạn khá đơn giản, nhưng việc quản lý các phần phụ thuộc của mã đó lại khó khăn hơn nhiều. Có tất cả các loại phần phụ thuộc: đôi khi có sự phụ thuộc vào một nhiệm vụ (chẳng hạn như "đẩy tài liệu trước khi tôi đánh dấu bản phát hành là hoàn chỉnh"), và đôi khi có một phần phụ thuộc trên cấu phần phần mềm (chẳng hạn như "Tôi cần có phiên bản mới nhất của thư viện thị giác máy tính để tạo mã của tôi"). Đôi khi, bạn có các phần phụ thuộc nội bộ trên một phần khác của cơ sở mã, và đôi khi bạn có các phần phụ thuộc bên ngoài vào mã hoặc dữ liệu thuộc sở hữu của một nhóm khác hoặc một tổ chức khác. Nhưng trong mọi trường hợp, ý tưởng “Tôi cần có trước khi có thể thực hiện được điều này” là điều gì đó lặp đi lặp lại trong quá trình thiết kế hệ thống xây dựng và việc quản lý các phần phụ thuộc có lẽ là công việc cơ bản nhất của hệ thống xây dựng.

Xử lý các mô-đun và phần phụ thuộc

Những dự án sử dụng hệ thống xây dựng dựa trên cấu phần phần mềm như Bazel được chia thành nhiều mô-đun, trong đó các mô-đun thể hiện phần phụ thuộc lẫn nhau thông qua tệp BUILD. Việc sắp xếp đúng cách các mô-đun và phần phụ thuộc này có thể ảnh hưởng rất lớn đến hiệu suất của hệ thống xây dựng cũng như lượng công việc cần để bảo trì.

Sử dụng các mô-đun tinh tế và quy tắc 1:1:1

Câu hỏi đầu tiên xuất hiện khi định cấu trúc bản dựng dựa trên cấu phần phần mềm là quyết định số lượng chức năng mà một mô-đun riêng lẻ sẽ cung cấp. Trong Bazel, một mô-đun được biểu thị bằng một mục tiêu chỉ định một đơn vị có thể tạo như java_library hoặc go_binary. Ở một mức độ cao nhất, toàn bộ dự án có thể nằm trong một mô-đun duy nhất bằng cách đặt một tệp BUILD ở thư mục gốc và nhóm đệ quy tất cả tệp nguồn của dự án đó. Mặt khác, gần như mọi tệp nguồn đều có thể được tạo thành mô-đun riêng, yêu cầu mỗi tệp được liệt kê trong một tệp BUILD mà các tệp khác cần đến.

Hầu hết các dự án đều nằm ở giữa những trường hợp cực đoan này, và sự lựa chọn đều liên quan đến sự đánh đổi giữa hiệu suất và khả năng bảo trì. Khi sử dụng một mô-đun duy nhất cho toàn bộ dự án, bạn có thể không cần làm gì vào tệp BUILD trừ phi thêm phần phụ thuộc bên ngoài, nhưng điều đó có nghĩa là hệ thống xây dựng phải luôn xây dựng toàn bộ dự án cùng một lúc. Điều này có nghĩa là nó sẽ không thể tải song song hoặc phân phối các phần của bản dựng, cũng như không thể lưu các phần đã được tạo vào bộ nhớ đệm. Một mô-đun cho mỗi tệp thì ngược lại: hệ thống xây dựng có độ linh hoạt tối đa trong việc lưu vào bộ nhớ đệm và lập lịch các bước của bản dựng, nhưng các kỹ sư cần nhiều công sức hơn để duy trì danh sách các phần phụ thuộc bất cứ khi nào họ thay đổi tệp nào tham chiếu đến.

Mặc dù độ chi tiết chính xác thay đổi tuỳ theo ngôn ngữ (và thường là cả trong ngôn ngữ), nhưng Google thường ưu tiên các mô-đun nhỏ hơn đáng kể so với mô-đun thường viết bằng hệ thống xây dựng dựa trên tác vụ. Một tệp nhị phân sản xuất điển hình tại Google thường phụ thuộc vào hàng chục nghìn mục tiêu và thậm chí một nhóm có quy mô vừa phải cũng có thể sở hữu hàng trăm mục tiêu trong cơ sở mã của mình. Đối với các ngôn ngữ như Java có tích hợp sẵn khái niệm đóng gói, mỗi thư mục thường chứa một gói, mục tiêu và tệp BUILD (Pants, một hệ thống xây dựng khác dựa trên Bazel, gọi quy tắc này là 1:1:1). Các ngôn ngữ có quy ước đóng gói yếu hơn thường xác định nhiều mục tiêu trong mỗi tệp BUILD.

Lợi ích của mục tiêu bản dựng nhỏ hơn thực sự bắt đầu thể hiện trên quy mô lớn vì chúng dẫn đến các bản dựng được phân phối nhanh hơn và nhu cầu tạo lại mục tiêu ít thường xuyên hơn. Ưu điểm trở nên hấp dẫn hơn sau khi hoạt động kiểm thử xuất hiện, vì các mục tiêu chi tiết hơn có nghĩa là hệ thống xây dựng có thể thông minh hơn nhiều khi chỉ chạy một tập hợp con kiểm thử giới hạn có thể bị ảnh hưởng bởi bất kỳ thay đổi nhất định nào. Vì Google tin vào lợi ích hệ thống của việc sử dụng các mục tiêu nhỏ hơn, nên chúng tôi đã có một số bước tiến trong việc giảm thiểu nhược điểm bằng cách đầu tư vào các công cụ để tự động quản lý các tệp BUILD nhằm tránh tạo gánh nặng cho nhà phát triển.

Một số công cụ trong số này, chẳng hạn như buildifierbuildozer, có sẵn cho Bazel trong thư mục buildtools.

Giảm thiểu mức độ hiển thị của mô-đun

Bazel và các hệ thống xây dựng khác cho phép mỗi mục tiêu chỉ định một chế độ hiển thị. Đây là một thuộc tính giúp xác định những mục tiêu khác có thể phụ thuộc vào mục tiêu đó. Mỗi mục tiêu riêng tư chỉ có thể được tham chiếu trong tệp BUILD của chính mục tiêu đó. Mục tiêu có thể cấp chế độ hiển thị rộng hơn cho các mục tiêu trong danh sách tệp BUILD được xác định rõ ràng, hoặc trong trường hợp hiển thị công khai, cho mọi mục tiêu trong không gian làm việc.

Tương tự như với hầu hết các ngôn ngữ lập trình, tốt nhất bạn nên giảm thiểu mức độ hiển thị. Nhìn chung, các nhóm tại Google sẽ chỉ công khai các mục tiêu nếu các mục tiêu đó đại diện cho những thư viện được sử dụng rộng rãi mà mọi nhóm tại Google đều có thể sử dụng. Các nhóm yêu cầu người khác phải phối hợp với họ trước khi sử dụng mã sẽ duy trì danh sách cho phép các mục tiêu khách hàng làm mục tiêu mà họ nhìn thấy. Mục tiêu triển khai nội bộ của mỗi nhóm sẽ chỉ bị giới hạn ở các thư mục do nhóm sở hữu và hầu hết các tệp BUILD sẽ chỉ có một mục tiêu không riêng tư.

Quản lý phần phụ thuộc

Các mô-đun cần phải có khả năng tham chiếu đến nhau. Nhược điểm của việc chia nhỏ cơ sở mã thành các mô-đun chi tiết là bạn cần phải quản lý các phần phụ thuộc giữa các mô-đun đó (mặc dù các công cụ có thể giúp tự động hoá việc này). Việc thể hiện các phần phụ thuộc này thường sẽ dẫn đến phần lớn nội dung trong tệp BUILD.

Phần phụ thuộc nội bộ

Trong một dự án lớn được chia thành các mô-đun chi tiết, hầu hết các phần phụ thuộc có khả năng là nội bộ; tức là trên một mục tiêu khác được xác định và tạo trong cùng một kho lưu trữ nguồn. Phần phụ thuộc nội bộ khác với phần phụ thuộc bên ngoài ở chỗ các phần phụ thuộc này được tạo từ nguồn thay vì được tải xuống dưới dạng cấu phần phần mềm tạo sẵn trong khi chạy bản dựng. Điều này cũng có nghĩa là không có khái niệm "phiên bản" cho các phần phụ thuộc nội bộ – một mục tiêu và mọi phần phụ thuộc nội bộ của mục tiêu đó luôn được tạo tại cùng một lần cam kết/sửa đổi trong kho lưu trữ. Một vấn đề cần xử lý cẩn thận liên quan đến các phần phụ thuộc nội bộ là cách xử lý các phần phụ thuộc bắc cầu (Hình 1). Giả sử mục tiêu A phụ thuộc vào mục tiêu B và mục tiêu này lại phụ thuộc vào mục tiêu thư viện C chung. Mục tiêu A có thể sử dụng các lớp đã xác định trong mục tiêu C không?

Phần phụ thuộc bắc cầu

Hình 1 Phần phụ thuộc bắc cầu

Theo các công cụ cơ bản thì không có vấn đề gì với điều này; cả B và C đều được liên kết vào mục tiêu A khi được tạo, vì vậy, A đều biết mọi ký hiệu được xác định trong C. Bazel đã cho phép điều này trong nhiều năm, nhưng khi Google phát triển, chúng tôi bắt đầu gặp phải nhiều vấn đề. Giả sử B đã được tái cấu trúc để không còn cần phụ thuộc vào C. Nếu phần phụ thuộc của B trên C bị xoá sau đó, thì A và mọi mục tiêu khác đã sử dụng C thông qua phần phụ thuộc trên B sẽ bị hỏng. Trên đó, các phần phụ thuộc của mục tiêu đã trở thành một phần của hợp đồng công khai và không bao giờ có thể thay đổi một cách an toàn. Điều này có nghĩa là các phần phụ thuộc tích luỹ theo thời gian và các bản dựng tại Google bắt đầu chậm lại.

Cuối cùng, Google đã giải quyết vấn đề này bằng cách giới thiệu một "chế độ phụ thuộc bắc cầu nghiêm ngặt" trong Bazel. Ở chế độ này, Bazel phát hiện xem một mục tiêu có cố gắng tham chiếu một biểu tượng mà không phụ thuộc trực tiếp vào biểu tượng đó hay không. Nếu có thì không thành công do lỗi và lệnh shell có thể dùng để tự động chèn phần phụ thuộc. Việc triển khai thay đổi này trên toàn bộ cơ sở mã của Google và tái cấu trúc từng mục tiêu trong số hàng triệu mục tiêu xây dựng để liệt kê rõ ràng các phần phụ thuộc của chúng là một nỗ lực trong nhiều năm, nhưng nó hoàn toàn xứng đáng. Giờ đây, các bản dựng của chúng tôi nhanh hơn nhiều vì các mục tiêu có ít phần phụ thuộc không cần thiết hơn, đồng thời các kỹ sư có thể xoá các phần phụ thuộc không cần thiết mà không phải lo lắng về việc phá vỡ các mục tiêu phụ thuộc vào các phần phụ thuộc đó.

Như thường lệ, việc thực thi các phần phụ thuộc bắc cầu nghiêm ngặt dẫn đến sự đánh đổi. Điều này khiến các tệp bản dựng trở nên chi tiết hơn, vì các thư viện thường dùng hiện cần được liệt kê một cách rõ ràng ở nhiều vị trí thay vì bị kéo vào một cách ngẫu nhiên. Các kỹ sư cũng phải tốn nhiều công sức hơn để thêm các phần phụ thuộc vào tệp BUILD. Kể từ đó, chúng tôi đã phát triển các công cụ giúp giảm bớt khối lượng công việc này bằng cách tự động phát hiện nhiều phần phụ thuộc bị thiếu và thêm chúng vào tệp BUILD mà không cần sự can thiệp của nhà phát triển. Tuy nhiên, ngay cả khi không có các công cụ như vậy, chúng tôi nhận thấy sự đánh đổi cũng xứng đáng khi cơ sở mã mở rộng quy mô: việc thêm phần phụ thuộc vào tệp BUILD là chi phí một lần, nhưng việc xử lý các phần phụ thuộc bắc cầu ngầm ẩn có thể gây ra sự cố miễn là mục tiêu bản dựng tồn tại. Bazel thực thi các phần phụ thuộc bắc cầu nghiêm ngặt trên mã Java theo mặc định.

Phần phụ thuộc bên ngoài

Nếu một phần phụ thuộc không phải là nội bộ, thì phần phụ thuộc đó phải là bên ngoài. Phần phụ thuộc bên ngoài là những cấu phần phần mềm được tạo và lưu trữ bên ngoài hệ thống xây dựng. Phần phụ thuộc được nhập trực tiếp từ kho lưu trữ cấu phần phần mềm (thường được truy cập qua Internet) và được sử dụng nguyên trạng thay vì được tạo từ nguồn. Một trong những điểm khác biệt lớn nhất giữa các phần phụ thuộc bên ngoài và nội bộ là các phần phụ thuộc bên ngoài có nhiều phiên bản và các phiên bản đó tồn tại độc lập với mã nguồn của dự án.

Quản lý phần phụ thuộc tự động và thủ công

Các hệ thống xây dựng có thể cho phép quản lý phiên bản của các phần phụ thuộc bên ngoài theo cách thủ công hoặc tự động. Khi được quản lý theo cách thủ công, tệp buildfile sẽ liệt kê rõ ràng phiên bản muốn tải xuống từ kho lưu trữ cấu phần phần mềm, thường sử dụng chuỗi phiên bản ngữ nghĩa chẳng hạn như 1.1.4. Khi được quản lý tự động, tệp nguồn sẽ chỉ định một loạt phiên bản được chấp nhận và hệ thống xây dựng luôn tải phiên bản mới nhất xuống. Ví dụ: Gradle cho phép khai báo một phiên bản phần phụ thuộc là "1.+" để chỉ định rằng mọi phiên bản nhỏ hoặc bản vá của một phần phụ thuộc đều được chấp nhận, miễn là phiên bản chính là 1.

Các phần phụ thuộc được quản lý tự động có thể thuận tiện cho các dự án nhỏ. Tuy nhiên, các phần phụ thuộc này thường là công thức dùng để gây ra thảm hoạ đối với các dự án có quy mô không nhỏ hoặc đang được nhiều kỹ sư xử lý. Vấn đề với các phần phụ thuộc được quản lý tự động là bạn không có quyền kiểm soát thời điểm cập nhật phiên bản. Không có cách nào để đảm bảo rằng các bên bên ngoài sẽ không thực hiện các bản cập nhật có thể gây lỗi (ngay cả khi họ tuyên bố sử dụng phiên bản ngữ nghĩa), vì vậy, một bản dựng hoạt động vào ngày nào đó có thể bị hỏng vào ngày tiếp theo mà không có cách nào dễ dàng để phát hiện nội dung thay đổi hoặc hoàn nguyên về trạng thái hoạt động. Ngay cả khi bản dựng không bị hỏng, có thể có những thay đổi nhỏ về hành vi hoặc hiệu suất mà bạn không thể theo dõi.

Ngược lại, vì các phần phụ thuộc được quản lý theo cách thủ công đòi hỏi phải thay đổi quyền kiểm soát nguồn, nên bạn có thể dễ dàng phát hiện và hoàn nguyên các phần phụ thuộc đó. Ngoài ra, bạn cũng có thể kiểm tra phiên bản kho lưu trữ cũ để xây dựng bằng các phần phụ thuộc cũ. Bazel yêu cầu chỉ định phiên bản của tất cả các phần phụ thuộc theo cách thủ công. Ở quy mô thậm chí vừa phải, chi phí quản lý phiên bản thủ công cũng xứng đáng với độ ổn định mà công cụ này mang lại.

Quy tắc một phiên bản

Các phiên bản khác nhau của một thư viện thường được biểu thị bằng các cấu phần phần mềm khác nhau, vì vậy, về lý thuyết, không có lý do gì mà các phiên bản khác nhau của cùng một phần phụ thuộc bên ngoài lại không thể khai báo cả hai trong hệ thống xây dựng bằng tên khác nhau. Bằng cách đó, mỗi mục tiêu có thể chọn phiên bản phần phụ thuộc muốn sử dụng. Điều này gây ra nhiều vấn đề trên thực tế, vì vậy, Google thực thi Quy tắc một phiên bản nghiêm ngặt cho tất cả các phần phụ thuộc bên thứ ba trong cơ sở mã của chúng tôi.

Vấn đề lớn nhất khi cho phép nhiều phiên bản là vấn đề về phần phụ thuộc của kim cương. Giả sử mục tiêu A phụ thuộc vào mục tiêu B và vào v1 của thư viện bên ngoài. Nếu sau đó mục tiêu B được tái cấu trúc để thêm một phần phụ thuộc trên phiên bản 2 của cùng một thư viện bên ngoài, thì mục tiêu A sẽ bị lỗi vì hiện mục tiêu này phụ thuộc ngầm vào 2 phiên bản khác nhau của cùng một thư viện. Trên thực tế, không bao giờ an toàn khi thêm một phần phụ thuộc mới từ mục tiêu vào bất kỳ thư viện bên thứ ba nào có nhiều phiên bản, vì bất kỳ người dùng nào của mục tiêu đó đều có thể đã phụ thuộc vào một phiên bản khác. Việc tuân thủ Quy tắc một phiên bản sẽ khiến xung đột này không thể xảy ra. Nếu một mục tiêu thêm phần phụ thuộc vào thư viện bên thứ ba, thì mọi phần phụ thuộc hiện có sẽ ở trên cùng một phiên bản đó, nhờ vậy, chúng có thể cùng tồn tại vui vẻ.

Phần phụ thuộc bên ngoài bắc cầu

Việc xử lý các phần phụ thuộc bắc cầu của một phần phụ thuộc bên ngoài có thể đặc biệt khó khăn. Nhiều kho lưu trữ cấu phần phần mềm, chẳng hạn như Maven Central, cho phép cấu phần phần mềm chỉ định các phần phụ thuộc trên các phiên bản cụ thể của các cấu phần phần mềm khác trong kho lưu trữ. Các công cụ xây dựng như Maven hoặc Gradle thường tải đệ quy từng phần phụ thuộc bắc cầu xuống theo mặc định, tức là việc thêm một phần phụ thuộc vào dự án có thể khiến hàng chục cấu phần phần mềm được tải xuống tổng cộng.

Điều này rất tiện lợi: khi thêm phần phụ thuộc vào một thư viện mới, bạn sẽ thấy rất khó khăn khi phải theo dõi từng phần phụ thuộc bắc cầu của thư viện đó rồi thêm tất cả theo cách thủ công. Tuy nhiên, cũng có một nhược điểm lớn: vì các thư viện khác nhau có thể phụ thuộc vào các phiên bản khác nhau của cùng một thư viện bên thứ ba, nên chiến lược này nhất thiết vi phạm Quy tắc một phiên bản và dẫn đến vấn đề phần phụ thuộc kim cương. Nếu mục tiêu của bạn phụ thuộc vào hai thư viện bên ngoài sử dụng các phiên bản khác nhau của cùng một phần phụ thuộc, thì không thể biết bạn sẽ nhận được thư viện nào. Điều này cũng có nghĩa là việc cập nhật một phần phụ thuộc bên ngoài có thể gây ra các lỗi dường như không liên quan trong toàn bộ cơ sở mã nếu phiên bản mới bắt đầu kéo các phiên bản xung đột của một số phần phụ thuộc.

Vì lý do này, Bazel không tự động tải các phần phụ thuộc bắc cầu xuống. Và rất tiếc là không có giải pháp toàn diện nào. Phương án thay thế của Bazel là yêu cầu một tệp chung liệt kê từng phần phụ thuộc bên ngoài của kho lưu trữ và một phiên bản rõ ràng dùng cho phần phụ thuộc đó trong toàn bộ kho lưu trữ. May mắn là Bazel cung cấp các công cụ có thể tự động tạo một tệp như vậy chứa các phần phụ thuộc bắc cầu của một tập hợp cấu phần phần mềm Maven. Bạn có thể chạy công cụ này một lần để tạo tệp WORKSPACE ban đầu cho một dự án. Sau đó, tệp đó có thể được cập nhật theo cách thủ công để điều chỉnh phiên bản của từng phần phụ thuộc.

Tuy nhiên, một lần nữa, bạn phải lựa chọn giữa sự tiện lợi và khả năng có thể mở rộng. Các dự án nhỏ có thể không muốn tự mình lo lắng về việc quản lý các phần phụ thuộc bắc cầu và có thể sử dụng các phần phụ thuộc bắc cầu tự động. Chiến lược này ngày càng trở nên kém hấp dẫn hơn khi tổ chức và cơ sở mã phát triển, đồng thời các xung đột và kết quả không mong muốn cũng trở nên thường xuyên hơn. Ở quy mô lớn hơn, chi phí quản lý các phần phụ thuộc theo cách thủ công ít hơn nhiều so với chi phí xử lý các vấn đề do tính năng quản lý phần phụ thuộc tự động gây ra.

Lưu kết quả bản dựng vào bộ nhớ đệm bằng các phần phụ thuộc bên ngoài

Các phần phụ thuộc bên ngoài thường được cung cấp bởi những bên thứ ba phát hành các phiên bản thư viện ổn định, có thể không cần cung cấp mã nguồn. Một số tổ chức cũng có thể chọn cung cấp một số mã của riêng họ dưới dạng cấu phần phần mềm, cho phép các đoạn mã khác phụ thuộc vào chúng dưới dạng của bên thứ ba thay vì các phần phụ thuộc nội bộ. Về mặt lý thuyết, điều này có thể tăng tốc các bản dựng nếu cấu phần phần mềm tạo quá chậm nhưng được tải xuống nhanh.

Tuy nhiên, việc này cũng dẫn đến nhiều chi phí và sự phức tạp: một người nào đó cần chịu trách nhiệm xây dựng từng cấu phần phần mềm đó và tải chúng lên kho lưu trữ cấu phần phần mềm, đồng thời ứng dụng cần đảm bảo luôn cập nhật phiên bản mới nhất. Việc gỡ lỗi cũng trở nên khó khăn hơn nhiều vì nhiều phần của hệ thống sẽ được xây dựng từ nhiều điểm trong kho lưu trữ, đồng thời không có chế độ xem nhất quán của cây nguồn.

Một cách tốt hơn để giải quyết vấn đề cấu phần phần mềm mất nhiều thời gian xây dựng là sử dụng một hệ thống xây dựng hỗ trợ lưu vào bộ nhớ đệm từ xa, như mô tả ở trên. Một hệ thống xây dựng như vậy sẽ lưu các cấu phần phần mềm kết quả từ mỗi bản dựng vào một vị trí được chia sẻ giữa các kỹ sư. Vì vậy, nếu nhà phát triển phụ thuộc vào một cấu phần phần mềm do người khác xây dựng gần đây, thì hệ thống xây dựng sẽ tự động tải cấu phần phần mềm đó xuống thay vì tạo cấu phần phần mềm đó. Điều này mang lại tất cả lợi ích về hiệu suất từ việc phụ thuộc trực tiếp vào cấu phần phần mềm trong khi vẫn đảm bảo rằng các bản dựng luôn nhất quán như thể các bản dựng đó luôn được tạo từ cùng một nguồn. Đây là chiến lược được Google sử dụng nội bộ và Bazel có thể được định cấu hình để sử dụng bộ nhớ đệm từ xa.

Tính bảo mật và độ tin cậy của các phần phụ thuộc bên ngoài

Việc phụ thuộc vào cấu phần phần mềm từ các nguồn của bên thứ ba vốn tiềm ẩn nhiều rủi ro. Bạn có thể gặp rủi ro về khả năng sử dụng nếu nguồn của bên thứ ba (chẳng hạn như kho lưu trữ cấu phần phần mềm) gặp sự cố, vì toàn bộ bản dựng của bạn có thể bị tạm dừng nếu không thể tải phần phụ thuộc bên ngoài xuống. Ngoài ra còn có rủi ro về bảo mật: nếu hệ thống bên thứ ba bị kẻ tấn công xâm phạm, thì kẻ tấn công có thể thay thế cấu phần phần mềm được tham chiếu bằng một trong các thiết kế của riêng họ, cho phép họ chèn mã tuỳ ý vào bản dựng của bạn. Cả hai vấn đề này đều có thể được giảm thiểu bằng cách phản chiếu bất kỳ cấu phần phần mềm nào bạn phụ thuộc vào các máy chủ mà bạn kiểm soát và chặn hệ thống xây dựng truy cập vào kho lưu trữ cấu phần phần mềm của bên thứ ba như Maven Central. Đổi lại, những bản sao này tốn nhiều công sức và tài nguyên để duy trì. Vì vậy, việc lựa chọn có sử dụng thường xuyên hay không sẽ phụ thuộc vào quy mô của dự án. Vấn đề bảo mật này cũng có thể được ngăn chặn hoàn toàn mà không tốn nhiều chi phí bằng cách yêu cầu chỉ định hàm băm của từng cấu phần phần mềm bên thứ ba trong kho lưu trữ nguồn. Điều này khiến bản dựng không hoạt động nếu cấu phần phần mềm bị can thiệp. Một cách khác có thể giúp tránh được vấn đề hoàn toàn là cung cấp các phần phụ thuộc của dự án. Khi cung cấp các phần phụ thuộc, dự án sẽ kiểm tra các phần phụ thuộc đó cùng với mã nguồn của dự án, dưới dạng nguồn hoặc tệp nhị phân. Điều này có nghĩa là tất cả các phần phụ thuộc bên ngoài của dự án sẽ được chuyển đổi thành các phần phụ thuộc nội bộ một cách hiệu quả. Google sử dụng phương pháp này trong nội bộ, kiểm tra mọi thư viện bên thứ ba được tham chiếu trên toàn Google vào một thư mục third_party ở gốc cây nguồn của Google. Tuy nhiên, cách này chỉ hoạt động tại Google vì hệ thống kiểm soát nguồn của Google được xây dựng tuỳ chỉnh để xử lý một monorepo cực kỳ lớn, vì vậy, việc cung cấp có thể không phải là lựa chọn cho tất cả các tổ chức.