Trình Soạn Thảo Tích Hợp (IDE) Không Phải Là Lựa Chọn Duy Nhất Của Lập Trình Viên (Phần 3)
Sau những phần giới thiệu trước, chắc hẳn những bạn ham học hỏi đã hiểu phần nào về công cụ make. Hãy ghi nhớ rằng việc viết file Makefile cũng là một phần quan trọng trong quá trình xây dựng phần mềm, không kém phần quan trọng so với việc viết các file mã nguồn .c hay .h. Khi sử dụng IDE, chính IDE sẽ tự động tạo ra các file tương đương với Makefile. Tuy nhiên, quá trình tạo ra này không hoàn toàn tự động - nó phụ thuộc vào các thao tác của bạn như click chuột, kéo thả file .c vào dự án, điền các biểu mẫu và đánh dấu các tùy chọn biên dịch.
Nếu toàn bộ các file nguồn trong dự án của bạn đều được viết bằng tay từ bàn phím, thì việc viết Makefile thủ công cũng là điều hoàn toàn hợp lý. Khi bạn lập trình bằng C++, sử dụng thư viện STL, bạn có bao giờ tự hỏi STL thực sự làm được những gì và cách thức hoạt động của nó ra sao không? Đây chính là thái độ cơ bản mà mỗi lập trình viên C/C++ cần có. Dù không nhất thiết phải nghiên cứu kỹ đến tận gốc rễ, nhưng ít nhất cũng nên hiểu sơ lược nguyên lý hoạt động. Điều này cũng đúng với công cụ Make - chúng ta cần hiểu rõ cách Make vận hành, ý nghĩa từng dòng lệnh trong Makefile, và tại sao những dòng lệnh đó có thể giúp hoàn thành công việc. Chuỗi bài viết này chọn Make làm chủ đề vì nguyên lý hoạt động của nó cực kỳ đơn giản, thuận tiện cho việc học tập.
Càng những thứ đơn giản càng có thể tạo ra nhiều điều kỳ diệu. Make cũng vậy. Tuy nhiên, nếu ngay từ đầu đã sử dụng các thư viện hoàn chỉnh do người khác viết sẵn, chúng ta sẽ dễ dàng đánh mất bản chất thực sự của vấn đề. Hãy tin tôi, cuối cùng việc viết Makefile có thể trở nên cực kỳ đơn giản. Khi đã thành thạo công cụ, bạn sẽ không cần viết bất kỳ dòng lệnh thừa thãi nào, thậm chí còn gọn gàng hơn cả việc kéo thả vài file nguồn trong IDE. Nhưng hành trình luôn bắt đầu từ những điều phức tạp - những điều phức tạp này chính là nền tảng giúp bạn hiểu sâu sắc, và khi đã hiểu rồi, bạn sẽ tự tìm được cách đơn giản hóa.
Tôi (Vân Phong) không phải là chuyên gia Make, xét theo một nghĩa nào đó, cũng chỉ là người mới bắt đầu. Khi viết loạt bài này, tôi cũng phải tra cứu tài liệu để kiểm chứng thông tin. Trong công việc hàng ngày, file Makefile thường phải qua nhiều lần gỡ lỗi mới hoạt động chính xác. Chính vì vậy, tôi mới thấu hiểu: làm thế nào để học và hiểu Make một cách hiệu quả, giúp vượt qua门槛 đầu tiên một cách dễ dàng.
Ở phần trước, chúng ta đã biết cách biên dịch riêng lẻ các file .c bằng dòng lệnh rồi liên kết chúng lại. Cách làm này giúp máy tính chỉ cần biên dịch lại những phần đã thay đổi, tiết kiệm thời gian. Với dự án nhỏ, hiệu quả không rõ rệt, nhưng với dự án lớn, lợi ích này rất đáng kể. Hãy nhớ rằng không có giải pháp tối ưu chung nào phù hợp với mọi trường hợp, vì việc triển khai giải pháp cũng tốn kém chi phí. Chúng ta chỉ cần tìm ra phương pháp trực tiếp và đơn giản nhất. Ví dụ, ở giai đoạn đầu dự án, bạn có thể viết một file Makefile cực kỳ đơn giản, rồi dần hoàn thiện khi dự án mở rộng.
Việc biên dịch riêng lẻ rồi liên kết lại này, với con người thì phiền phức, nhưng giao cho máy tính thực hiện còn phiền phức hơn. Vì vậy phần trước tôi không đi sâu chi tiết. Những bạn ham học hỏi hẳn đã tự mày mò được, hôm nay tôi sẽ chia sẻ phương pháp của mình. Nhưng trước tiên, hãy cùng hệ thống lại kiến thức về Make.
Make là công cụ hoạt động theo mô hình cực kỳ đơn giản. Trong lòng nó chứa một bảng ghi chép mối quan hệ phụ thuộc giữa các file mục tiêu. Makefile chính là công cụ để mô tả bảng phụ thuộc này. Cú pháp mô tả mối quan hệ này vô cùng đơn giản:
|
|
Điều này có nghĩa là “mục_tiêu” cần được xây dựng dựa trên “phụ_thuộc” đã tồn tại. Ở đây, cả “mục_tiêu” và “phụ_thuộc” đều là các file trong hệ thống. “Phụ_thuộc” cũng có thể là một “mục_tiêu” khác. Nếu thời gian sửa đổi của “phụ_thuộc” mới hơn “mục_tiêu”, điều đó có nghĩa là “mục_tiêu” cần được xây dựng lại. Nếu “mục_tiêu” chưa tồn tại, quá trình xây dựng cũng sẽ được kích hoạt.
Một mục tiêu có thể phụ thuộc vào nhiều yếu tố, được viết cách nhau bằng khoảng trắng sau dấu hai chấm:
|
|
Đây là điều chúng ta đã gặp nhiều ở phần trước. Ngoài ra còn có một quy tắc khác: chúng ta có thể viết riêng từng dòng:
|
|
Hai cách viết này hoàn toàn tương đương. Ví dụ:
|
|
và
|
|
là như nhau.
Phương pháp xây dựng mỗi mục tiêu không được tích hợp sẵn trong Make. Make chỉ đơn thuần gọi các lệnh shell được viết ở dòng tiếp theo của định nghĩa mục tiêu. Mỗi dòng lệnh shell phải bắt đầu bằng phím Tab. Lưu ý rằng nếu bạn chia nhỏ các phụ thuộc của một mục tiêu thành nhiều dòng, chỉ có duy nhất một nơi được định nghĩa script xây dựng.
Ví dụ:
|
|
Khi chạy make, kết quả sẽ là:
|
|
Bởi vì Make luôn chọn mục tiêu đầu tiên trong Makefile làm mục tiêu cuối cùng cần xây dựng - ở đây là “all”. “all” phụ thuộc vào “a” và “b”. Vì thư mục làm việc không tồn tại “a” và “b”, nên quá trình xây dựng “a” và “b” được kích hoạt. Lệnh xây