Tôi thích Makefile
(switowski.com)- Dù các công cụ build mới liên tục xuất hiện, Makefile vẫn hữu ích như một công cụ mặc định nhẹ để gom các tác vụ lặp lại của dự án
- Nhờ các lệnh mang tính quy ước như
make,make build,make install,make dev, ngay cả với dự án lần đầu thấy bạn cũng có thể nhanh chóng thử build, cài đặt và chạy server phát triển - Dù stack khác nhau như Jekyll, Hugo, 11ty, các framework web Python, Docker hay gulp, Makefile vẫn đóng vai trò là giao diện thực thi chung
- Trong các dự án cá nhân, vài target đơn giản như
dev,build,deploythường là đủ, thay vì điều kiện hay flag - GNU Make rất phổ biến; ngay cả khi không có, bạn vẫn có thể chạy trực tiếp các lệnh shell trong Makefile, nên gánh nặng nhỏ ngay cả trong môi trường bị hạn chế
Gom các lệnh khác nhau của từng dự án vào một điểm vào duy nhất
- Makefile được dùng để bọc các dự án có cách build, chạy và triển khai khác nhau bằng một tập lệnh quen thuộc
- Nếu một dự án lần đầu thấy có
Makefile, thường có thể bắt đầu thử bằng các lệnh saumakemake buildmake install
- Khi mở lại một dự án cá nhân cũ, bạn cũng có thể cấu hình để chỉ cần chạy
make devlà xử lý cùng lúc các bước build cần thiết và chạy server phát triển - Dù stack công nghệ khác nhau như Jekyll, Hugo, 11ty hay nhiều framework web Python, vẫn có thể duy trì cùng một điểm vào
- Ngay cả khi dùng Docker hoặc gulp, Makefile không thay thế các công cụ đó mà hoạt động như một lớp điều phối
- Ví dụ:
make buildbuild các image Docker cần thiết, đồng thời truyền thêm tham số riêng theo từng dự án
- Ví dụ:
Cách dùng đơn giản trong dự án cá nhân
- Việc dùng Makefile tập trung vào gom các lệnh shell hơn là các tính năng phức tạp
- Không dùng các tính năng như điều kiện hay flag
- Đơn vị công việc của make về mặt kỹ thuật là target, nhưng trong thực tế được xem như task
- Có thể tạo bằng hàm trong script bash, nhưng tác giả cho rằng Makefile dễ và nhanh hơn
- Các tác vụ thường dùng trong dự án cá nhân thường chỉ có 2–3 cái
dev: khởi động server phát triểnbuild: build dự án khi cầndeploy: triển khai hoặc xuất bản dự ánwatch: dùng như tác vụ bổ sung để tự động chạy lại build khi file nguồn thay đổi
- Makefile của blog hiện tại chỉ có một target
dev- Chạy
npm run devbên dướidev:
- Chạy
- Trong các dự án cá nhân phức tạp hơn, các tác vụ sau được gom bằng Makefile
dev: chạy server phát triển bằngbundle exec jekyll servebuild: build tài nguyên bằngnpm run gulp buildwatch: theo dõi một thư mục cụ thể bằngnpm run gulp watchdeploy: sau khi build Jekyll trong môi trường production, chạymake encrypt,netlify deploy --prodencrypt: mã hóa các file HTML trong thư mục_sitebằngnpx staticrypt
Những hạn chế và ưu điểm cần biết
- Trong ví dụ đã lược bỏ phony targets, nhưng nếu có file trùng tên như
dev,build,watch,deploy,encrypt, Makefile có thể không hoạt động như mong đợi- Độc giả Hacker News khuyến nghị thêm phony target trong những trường hợp này
- GNU Make là phần mềm chạy makefile; nó nhiều khả năng đã được cài sẵn trên Linux và phổ biến đến mức trên MacBook cũng khó nhớ là có cần cài riêng hay không
- Make không đòi hỏi nhiều phụ thuộc bổ sung, nên có thể hữu ích trong môi trường bị hạn chế, nơi việc cài package khó hoặc không thể thực hiện vì lý do bảo mật
- Ngay cả khi không có Make, bạn vẫn có thể sao chép trực tiếp các lệnh trong Makefile vào shell để chạy
- Ngược lại, nếu server không có gulp, sẽ khó dán nguyên mã JavaScript vào terminal để chạy; đây là điểm khác biệt
- Dù bạn tìm thấy một công cụ build mới tốt hơn và nhanh hơn, Make vẫn có thể tiếp tục được dùng như một lớp lệnh chung để quản lý các công cụ và cấu hình khác nhau
2 bình luận
Tôi thích Makefile
Nếu một
makefilekhông định nghĩa các dependency, thì thay bằngjustfilesẽ mang lại trải nghiệm sử dụng tốt hơn.Ý kiến trên Hacker News
Không cần nản lòng trước những lời trong luồng này rằng “bạn đang dùng make sai cách”. Một trong những lý do Make là công cụ tốt là nó có thể bắt đầu đơn giản hơn vẻ ngoài của nó
Không dùng
.PHONYcó thể gây vấn đề, nhưng với dự án nhỏ thì đó có thể là cái bẫy phải một năm sau mới đạp phải, và khi đó nhiều khả năng chỉ cần gãi đầu khoảng một giờ là xong. Trong 99% trường hợp, bạn có thể bắt tay làm ngay mà không cần bận tâm đến “chuẩn mực”, và Make chỉ đòi hỏi mức độ phức tạp vừa đủ để không sụp đổMake nhận các tệp đầu vào và phụ thuộc để tạo ra chính xác một tệp đầu ra. Các quy tắc không tạo đầu ra như
install,all,clean, hoặc tất cả target trong bài viết, phải phụ thuộc vào một target ma thuật đặc biệt như.PHONY; nhưng.PHONYkhông có trong POSIX năm 2017 (IEEE Std 1003.1-2017, https://pubs.opengroup.org/onlinepubs/9699919799/utilities/m...) và chỉ mới được đưa vào tiêu chuẩn hiện tại (IEEE Std 1003.1-2024, https://pubs.opengroup.org/onlinepubs/9799919799/utilities/m...). Nếu muốn tạo nhiều tệp như object file, module, header biên dịch sẵn, bạn phải tự viết những mẹo dễ vỡ. Không phải Make nào cũng là GNU Make, và các hệ kiểu nix như BSD, Solaris/Illumos vẫn còn tồn tạiĐiều đó không có nghĩa Make vô dụng. Nó hữu ích cho những dự án đủ phức tạp nhưng chưa đến mức cần một hệ thống build “tốt hơn”. Vấn đề là các dự án như vậy sẽ phức tạp hơn khi mã tăng lên, rồi cuối cùng có script/chương trình sinh ra Makefile hoặc một phần của nó, tạo thành một hệ meta-build tạm bợ
Vấn đề lớn hơn không phải là dùng nó, mà là đề xuất nó như một giải pháp cho “tất cả mọi người”. Và ngay khi trong thư mục gốc dự án xuất hiện một thư mục hay tệp tên
buildhoặcdev, Makefile đó sẽ ngừng hoạt độngDạo này tôi chuyển sang pnpm, nhưng vẫn không thấy nó quá phức tạp. Đúng là cần biết cú pháp ở mức nào đó, nhưng vẫn đỡ khó hơn so với việc làm cho các công cụ khác chạy đúng ngay từ đầu
.PHONYmà mọi người hay bận tâm đến vậyNó đơn giản, dễ đọc, có thể chỉnh sửa, có thể kết hợp, và hầu như ở đâu cũng đã được cài sẵn. Nó làm đúng những gì ghi trên bề mặt và gần như không làm gì hơn
Nhân tiện, khi phải xử lý các hệ thống build theo trào lưu hoặc các hệ thống build ác mộng trong những dự án khác, tôi cũng bọc chúng bằng make
Ai cũng phải bắt đầu từ đâu đó, và một khi đã chạy được thì có thể dễ dàng bổ sung cải tiến dần dần
$@. Toàn bộ Makefile có thể tạo số lượng đầu ra tùy ý vì quy tắc này có thể phụ thuộc vào quy tắc khácTừ đây có một quy tắc kinh nghiệm gọn gàng cho target phony. Nếu recipe không đụng đến
$@thì nên đánh dấu$@là phony. Quản lý các target phony bằng một danh sách sẽ dễ hơn nhiềuphonies :=phonies += somethingsomething:./do-somethingphonies += something-elsesomething-else: something./do-something-else# touches $@ and thus does not need to be phonycreate-file:./generate-some-output > $@.PHONY: $(phonies)Makefile là một công nghệ tệ hại. Chỉ là nó đỡ tệ hơn một chút so với phần lớn các hệ thống build khác mà chúng ta tạo ra, nên trở nên hữu ích theo nghĩa tự hành hạ bản thân
Các hệ thống build thường hoặc quá sơ khai nên hễ build thứ gì hơn đồ chơi là thành hỗn loạn, hoặc quá phức tạp nên đòi hỏi quá nhiều kiến thức nền, quan liêu, đồng bộ và mã khuôn mẫu, hoặc không có thư viện chuẩn nên cùng một mẫu build bị phân tán thành 10.000 cách triển khai không tương thích, hoặc quá hạn chế nên khi nhu cầu tăng lên lại phải di chuyển sang hệ khác, hoặc có quá nhiều phép màu, hoặc cú pháp khó hiểu và thiếu nhất quán
Hơn một nửa phần mềm tôi dùng chỉ sử dụng Makefile bình thường và đôi khi thêm script configure. Không cần autotools, chỉ tùy chọn chạy
./configurerồimake,make installlà chạy được. Đây hoàn toàn không phải cấu hình đồ chơi, mà được cấu thành từ những chương trình nhỏ làm tốt một việcMake và cách nó hoạt động, ít nhất với tôi, rất dễ hiểu. Tôi nghĩ trải nghiệm tệ mà mọi người gặp thường là do cấu hình build tệ của các dự án phức tạp hoặc thiếu kiến thức cần thiết, hơn là do bản thân Make
Việc không có thư viện chuẩn là nhằm không phụ thuộc công cụ, nhờ đó nó có thể áp dụng phổ quát cho nhiều công cụ, ngôn ngữ và mục đích khác nhau. Tôi xem đó là tính năng chứ không phải khiếm khuyết. Lớp đơn giản đó là theo dõi phụ thuộc, target và dependency cũng rõ ràng, nên khó có thể gọi là ma thuật. Cú pháp thì chắc chắn có thể cải thiện hơn
Các hệ thống build đa dụng bao quát nhiều ngôn ngữ chỉ có
makehoặc những hệ thống nhắm đếnmakenhư CMake. Lý do chúng không hay là vìmakekhông hay.makethật sự rất khó dùng cho đúng, có các vấn đề như recursive make và non-recursive make, có nhiều biến thể không tương thích như Unix/POSIX make, BSD make, GNU make, Windowsnmake, và trông cũng khá xấu xíDù vậy,
makenếu được dùng đúng thì khá tốt. Thật may là chúng ta cómakelàm mẫu số chung nhỏ nhấtViệc Make thậm chí không xử lý gọn gàng được thư mục con là khá khó hiểu. Tôi tò mò không biết có gì tốt hơn Make không. Có Ninja, nhưng nó không được thiết kế để con người viết trực tiếp
Bạn nghĩ mức độ là bao nhiêu? Chẳng phải khoảng 20%, 40%, 70% sao
Tôi thật sự thích Make. Trong 20 năm qua, với mọi công việc hay dự án từng đụng tới, tôi đều viết ít nhất một cái
Không có gì thông minh cả, chỉ là tập hợp các mảnh ghép với vài biến. Kiểu như
make run,make test,make lintmake recentlint rồi chạy script được sửa gần đây nhất. Có thể làm bằng Bash hay shell khác, nhưng như vậy sẽ bị hút vào thế giới developer và chỉ tăng độ phức tạp mà không có thêm giá trị. Make chỉ là một ngôn ngữ chuyên biệt miền cho việc “file kiểu này được tạo từ file kiểu kia, và lúc đó chạy một hai lệnh này”. Vì thế nó cực kỳ mạnhNhiều thập kỷ trước tôi từng tạo một framework test bằng Java, cho phép chỉ định test và các class phụ thuộc bằng cú pháp giống make. Có một tập các class test định nghĩa “bộ test chuẩn”, rồi các tầng class test khác phụ thuộc lên đó chỉ được chạy nếu tầng phía trước thành công
Tôi không hiểu vì sao ngày nay mọi người lại làm mọi thứ phức tạp đến vậy. Tôi luôn khuyên hãy bám vào các công cụ Unix chuẩn và cách làm của chúng, tức những phương pháp đã được thời gian kiểm chứng, trừ khi rơi vào tình huống tuyệt đối không thể làm như vậy. Thời gian là hữu hạn, nên tôi muốn dùng nó cho phần cốt lõi của giải quyết vấn đề như thiết kế hệ thống/chương trình, mô hình hóa, cấu trúc, pattern, v.v.; phần còn lại chỉ là thứ yếu
Biết rằng bất kể stack, ngôn ngữ hay repo nào, cứ chạy
make testlà hoạt động, quả là một sự giải thoát lớnMakefile của dự án trước có bốn lệnh chính:
build,test,frontend,deploy. build và test gọi maven, frontend gọi npm, deploy gọi docker và awsTất cả các công cụ này đều có cơ chế theo dõi trạng thái, caching và xử lý gia tăng riêng, nhưng không báo cáo chúng đã làm gì. Vì vậy không thể viết các quy tắc ở mức phân tử như “chỉ deploy khi build đã được cập nhật”. Vì maven/cargo/dotnet/npm/go không phơi bày thông tin đó
Tác giả thậm chí không dùng theo dõi phụ thuộc dựa trên mtime. Các target cũng đáng ra phải là
.PHONYnhưng lại không được đánh dấu. Hoàn toàn có thể thay bằng một shell script đọc$1, so khớp rồi quyết định việc cần làmhttps://just.systems/
.PHONY: $(MAKECMDGOALS)./build.sh devđúng là kém rõ ràng hơnmake dev. Một lý do khác để dùng Make ngay cả khi không có bước non-phony nào là về sau, nếu cần, ta có thể thêm các bước đó. Tất nhiên tôi đồng ý rằng tác giả nên đánh dấu{dev,build,deploy}là phonyMakefile của họ có thể cải thiện về mặt kỹ thuật, nhưng điều quan trọng hơn là trải nghiệm “bắt đầu” khi vào một dự án được nhất quán
Người ta muốn nó làm thật nhiều việc khác như ngôn ngữ script, trình theo dõi phụ thuộc, v.v., rồi kéo vào những công cụ phình to. Mới hơn không nhất thiết là tốt hơn. Autoconf và automake không phải là make
Về mặt kỹ thuật, các target make này đều tìm một file có cùng tên với tên target. Mỗi target thực ra nên được định nghĩa là
.PHONYTrước đây tôi cũng luôn dùng những Makefile kiểu này, nhưng vài năm gần đây đã chuyển sang just và justfile. Hành vi này là mặc định và nhìn chung dễ dùng hơn. Những thứ như tham số cũng đơn giản hơn
https://github.com/casey/just
:Tất nhiên giờ thì đã quá muộn
Có lẽ đã đến lúc xuất hiện một bản phân phối coreutils mới, gom các công cụ mới và hữu ích như just, ripgrep để cài ở bất cứ nơi nào ta muốn dùng
Thật thú vị khi make lại khơi ra tranh luận dữ dội như vậy. Nó giống các cuộc tranh luận vi và emacs ngày trước, gần như nửa chiến tranh tôn giáo
Tôi hoàn toàn đồng ý với bài gốc, và đặc biệt cho rằng cách bọc các công cụ build tinh vi hơn bằng một Makefile cấp cao nhất là thông minh. Lợi ích là chuẩn hóa. Không cần nhớ gì cả; nếu là dự án tôi viết thì chỉ cần gõ
makelà biết nó sẽ chạyVí dụ, khi một người dùng C muốn biên dịch một dự án Rust, họ không cần tra cách dùng cargo mà chỉ cần gõ
makehoặcgmake. Tôi cố gắng tuân thủ POSIX và không dùng tính năng đặc thù của GNU, nhưng cũng đúng là gần như 100% make ngoài đời là gmakeĐề xuất dùng
makevượt thời gian như một kiểu trình điều phối hệ thống build cấp cao nhất có lẽ vẫn sẽ chạy sau 250 năm nữacargo buildBản thân Make thì tuyệt vời, nhưng trong hầu hết các trường hợp tôi làm với dự án C, đó là tổ hợp cmake/autotools và cài package toàn cục, và những thứ đó thì thường phải tra cứu
Tôi đồng ý với tinh thần ở đây, nhưng đang viết lại nhiều thứ bằng Justfile
https://github.com/casey/just
Có thể tránh được rất nhiều điểm kỳ quặc riêng của Makefile
Đúng, Make rất tuyệt. Tôi dùng nó cho rất nhiều việc. Đây là một cách tốt để tự động hóa tác vụ
Ví dụ, website cá nhân của tôi dùng Makefile gọi bash script để tạo lại các trang web đã cập nhật, git push lên server, rồi git hook trên server gọi Make để triển khai. Tuy nhiên, tôi không muốn đưa các khối binary thường xuyên thay đổi như PDF tài liệu bài giảng vào kho Git. Không sao cả. Makefile có target
uploadschỉ tải các PDF đã sửa đổi lên server, và target này là phụ thuộc của targetdeploythực hiện git push, nên tôi không cần bận tâmNgoài ra, các PDF tài liệu bài giảng đã cập nhật được một Makefile khác tự động đưa vào cây source của website. Với Makefile đó, tôi có thể tạo PDF mình dùng từ source LaTeX, hoặc tạo các phiên bản thay thế cho sinh viên từ cùng source, chẳng hạn phiên bản không có lời giải bài tập, tự động đăng lên phiên bản website local rồi khi muốn thì đưa lên website đã cập nhật
Đó là một kiểu cấu trúc Makefile nối tiếp bất tận. Tôi thích Makefile
Trong trường hợp này, thứ tác giả thật sự cần không phải make mà là just
https://just.systems/man/en/
Với vai trò trình chạy tác vụ, make không tệ, nhưng ngày nay có những lựa chọn thay thế tốt hơn như just, như các bình luận khác đã nói
Với vai trò hệ thống build, make ổn cho đến khi bạn gặp các nhược điểm của nó. make/Makefile không được chuẩn hóa, nên automake mới tồn tại. Vì vậy bạn không viết Makefile trực tiếp mà dùng template rồi để nó tạo makefile thật. Nếu bạn tự sở hữu toàn bộ toolchain thì không sao, nhưng đa số không như vậy, nên người ta làm thế để bảo đảm Makefile có tính di động
make không thực hiện bất kỳ kiểu phân giải phụ thuộc nào và giả định rằng những thứ cần thiết đã ở ngay đó. Vì vậy mới có script configure; mà script configure cũng không phải chuẩn, nên trước khi chạy target của make, bạn lại tạo script configure bằng autoconf/autoreconf
make và các công cụ lân cận như automake/autoconf/autoreconf dùng mtime khi xác định đầu vào có bị cũ hay không. Ngay cả khi chạy autoconf/autoreconf/automake/configure, bạn vẫn có thể rơi vào tình huống đầu vào mãi bị coi là cũ và không build được gì. Nhân tiện, nhiều hệ thống build dùng mtime vẫn ổn nếu có theo dõi phụ thuộc đúng cách
Nhìn chung, khiếm khuyết thiết kế căn bản của make là nó được xây theo triết lý Unix, tức “làm một việc và làm thật tốt”. Việc đó là “nếu đầu vào đã cũ thì build lại target”, nhưng đây là một công cụ rất hạn chế, nên các hệ thống build hiện đại phải chồng rất nhiều thứ lên trên nó để khiến nó hữu ích hơn một trình chạy tác vụ cơ bản
makekhông phân giải phụ thuộc ư?dependency:...target: dependency...make configure. Cái này thiết lập những thứ cần chomake [build]Vì vậy cuối cùng phải dùng touchfile; tuy hơi bừa bộn nhưng vẫn hoạt động tốt hơn nhiều thứ khác. Tôi nói vậy đặc biệt là khi nhìn vào caching của docker build