Những lợi thế của các kịch bản xây dựng là gì?


97

Trong phần lớn sự nghiệp lập trình của mình, tôi đã sử dụng lệnh "xây dựng / biên dịch / chạy" trong bất kỳ IDE nào tôi đang làm việc để tạo ra một chương trình có thể chạy được. Đây là một nút, khá dễ dàng. Tuy nhiên, khi tôi tìm hiểu thêm về các ngôn ngữ và khung khác nhau, tôi thấy ngày càng nói nhiều hơn về "xây dựng tập lệnh" (ANT, Maven, Gradle, v.v.) để chạy dự án. Sự hiểu biết của tôi về những điều này là chúng là các hướng dẫn cho trình biên dịch / trình liên kết / nhà sản xuất chương trình ma thuật xác định chi tiết cấu hình - minutiae.

Tôi nhớ đã viết makefiles hồi còn đi học, nhưng sau đó tôi không thấy bất kỳ lợi thế đặc biệt nào (chúng tôi chỉ sử dụng chúng khi viết trong thiết bị đầu cuối Unix, nơi không có IDE với nút "xây dựng" tiện dụng). Ngoài ra, tôi đã thấy các câu hỏi khác ở đây thảo luận về cách xây dựng tập lệnh có thể làm nhiều hơn là chỉ tạo chương trình của bạn - chúng có thể chạy thử nghiệm đơn vị cũng như bảo mật tài nguyên bất kể máy chủ .

Tôi không thể lay chuyển được cảm giác rằng các kịch bản xây dựng rất quan trọng để hiểu như một nhà phát triển, nhưng tôi muốn một lời giải thích có ý nghĩa; Tại sao tôi nên sử dụng / viết tập lệnh xây dựng?

Trách nhiệm của Build Script và Build Server thảo luận về vai trò của nó trong phạm vi rộng hơn. Tôi đang tìm kiếm những lợi thế cụ thể được cung cấp bởi tập lệnh xây dựng so với lệnh "xây dựng / chạy" của IDE hoặc các phương thức đơn giản tương tự.


18
các câu trả lời ở đây đã bao quát mọi thứ khá tốt, nhưng tôi muốn đề cập đến thực tế là khi bạn nhấp vào nút "chạy" trong IDE của mình, nó (gần như luôn luôn) thực thi một tập lệnh xây dựng mà IDE của bạn tạo ra. viết kịch bản xây dựng của riêng bạn chỉ giúp bạn kiểm soát quá trình nhiều hơn.
Woodrow Barlow

6
viết và chia sẻ tập lệnh xây dựng của riêng bạn cũng có nghĩa là bất kỳ ai cũng có thể xây dựng nó với một quy trình giống hệt với quy trình của bạn ngay cả khi người khác sử dụng IDE khác. Điều này giúp với sự nhất quán.
Woodrow Barlow


1
Xây dựng kịch bản thường important to understand as a developer, mặc dù chắc chắn không phải lúc nào. Ngay cả trong môi trường mà các tập lệnh xây dựng là yêu cầu thực tế, nhiều "nhà phát triển" sẽ không quan tâm đến chúng trong một chút. Nhưng các kịch bản sau đó rất quan trọng đối với 'người xây dựng' hơn là đối với các nhà phát triển. Những nơi cuối cùng tôi làm việc, hầu hết các nhà phát triển thực tế không có kết nối để xây dựng các tập lệnh.
dùng2338816

3
không phải ai cũng sử dụng IDE với nút "xây dựng"
Vladimir Starkov

Câu trả lời:


112

Tự động hóa.

Khi bạn đang phát triển, chỉ trong các dự án đơn giản nhất, nút "xây dựng" mặc định sẽ làm mọi thứ bạn cần nó để làm; bạn có thể cần tạo WS ngoài API, tạo tài liệu, liên kết với các tài nguyên bên ngoài, triển khai các thay đổi cho máy chủ, v.v. Một số IDE cho phép bạn tùy chỉnh quy trình xây dựng bằng cách thêm các bước hoặc trình tạo bổ sung, nhưng điều đó chỉ có nghĩa là bạn tạo tập lệnh xây dựng của bạn thông qua các hàng hóa IDE.

Nhưng phát triển một hệ thống không chỉ là viết mã. Có nhiều bước liên quan. Một tập lệnh độc lập IDE có thể được thực thi tự động, có nghĩa là:

  • Khi bạn cam kết thay đổi kiểm soát phiên bản, máy chủ mới có thể được khởi chạy tự động. Nó sẽ đảm bảo rằng bạn không quên cam kết bất cứ điều gì cần thiết cho việc xây dựng.

  • Tương tự, sau khi quá trình xây dựng hoàn tất, các bài kiểm tra có thể tự động được chạy để xem bạn có phá vỡ thứ gì không.

  • Bây giờ phần còn lại của tổ chức (QA, sysadins) đã có một sản phẩm được xây dựng

    a) hoàn toàn có thể tái tạo chỉ từ phiên bản điều khiển.

    b) là chung cho tất cả chúng.

Ngay cả khi tôi làm việc như một nhóm một người, tôi đã sử dụng các kịch bản cho mục đích đó; Khi tôi đã phát triển bản sửa lỗi, tôi sẽ cam kết với SVN, xuất SVN trở lại trong một thư mục khác và sử dụng tập lệnh xây dựng để tạo giải pháp, sau đó sẽ chuyển đến các hệ thống Tiền sản xuất và sau đó là Sản xuất. Nếu một vài tuần sau (với cơ sở mã địa phương của tôi đã thay đổi) ai đó đã phàn nàn về một lỗi, tôi sẽ biết chính xác bản sửa đổi SVN nào tôi sẽ phải kiểm tra để gỡ lỗi hệ thống.


4
Đây là câu trả lời tốt nhất IMHO. Mọi người đều đúng với ý tưởng của họ, nhưng điều này thực sự cho thấy lý do tại sao bạn muốn xây dựng các kịch bản. Bởi vì chúng hỗ trợ tự động hóa trong tương lai khi bạn mở rộng dự án sẽ giúp bạn tiết kiệm hàng tấn thời gian.
Alexus

16
@Alexus Không chỉ là thời gian, mà cả những sai lầm ngớ ngẩn nữa. ;) "Sự lặp lại dẫn đến sự nhàm chán. Sự nhàm chán dẫn đến những sai lầm khủng khiếp. Những sai lầm khủng khiếp dẫn đến, 'Tôi ước mình vẫn còn buồn chán.' "(Bạn cũng có thể đo lường những sai lầm ngớ ngẩn theo thời gian, nhưng điều quan trọng là phải nhận ra rằng điều đó giúp bạn tiết kiệm thời gian khỏi một số nguyên nhân.)
jpmc26

@ jpmc26 đang ở đó - thực hiện điều đó: D
Alexus

2
Ngay cả đối với các dự án một người, việc chạy một máy chủ Jenkins cục bộ để tự động hóa "xây dựng trong thư mục riêng biệt" có thể giúp ích. Tôi đang làm việc trên một cái gì đó trong Windows mà tôi cần xác nhận cũng biên dịch chéo, do đó, việc Jenkins xây dựng và chạy thử nghiệm, sau đó xây dựng phiên bản được biên dịch chéo giúp tôi tự tin hơn (và hiệu quả, tất nhiên!) Khi tôi cần phát hành sửa lỗi.
Ken YN

4
@ JonasGröger Tự động hóa đưa ra một trong những biến số lớn nhất: con người.
CurtisHx

34

Giống như mã, một tập lệnh xây dựng được thực hiện bởi máy tính. Máy tính đặc biệt tốt trong việc làm theo một bộ hướng dẫn. Trong thực tế, (bên ngoài mã tự sửa đổi), các máy tính sẽ thực hiện cùng một chuỗi các hướng dẫn chính xác theo cùng một cách, được đưa ra cùng một đầu vào. Điều này mang đến một mức độ nhất quán mà, chỉ có một máy tính có thể phù hợp.

Ngược lại, túi thịt chứa đầy nước của chúng tôi hoàn toàn đáng khinh khi đi theo các bước sau. Bộ não phân tích phiền phức đó có xu hướng đặt câu hỏi về mọi thứ nó đi qua. "Ồ ... tôi không cần điều đó", hoặc "Tôi có thực sự sử dụng lá cờ này không? Ơ ... tôi sẽ bỏ qua nó." Ngoài ra, chúng ta có xu hướng tự mãn. Khi chúng tôi đã làm điều gì đó một vài lần, chúng tôi bắt đầu tin rằng chúng tôi biết các hướng dẫn và không phải xem bảng hướng dẫn.

Từ "Lập trình viên thực dụng":

Ngoài ra, chúng tôi muốn đảm bảo tính nhất quán và độ lặp lại cho dự án. Thủ tục thủ công để lại sự nhất quán để thay đổi; độ lặp lại không được đảm bảo, đặc biệt nếu các khía cạnh của quy trình được mở để giải thích bởi những người khác nhau.

Ngoài ra, chúng tôi rất chậm chạp trong việc thực hiện các hướng dẫn (so với máy tính). Trên một dự án lớn, với hàng trăm tệp và cấu hình, sẽ mất nhiều năm để thực hiện thủ công tất cả các bước trong quy trình xây dựng.

Tôi sẽ cho bạn một ví dụ thực tế. Tôi đã làm việc với một số phần mềm nhúng, trong đó phần lớn mã được chia sẻ trên một vài nền tảng phần cứng khác nhau. Mỗi nền tảng có phần cứng khác nhau, nhưng hầu hết các phần mềm đều giống nhau. Nhưng có những mảnh nhỏ dành riêng cho từng phần cứng. Lý tưởng nhất là các phần chung sẽ được đặt trong thư viện và liên kết thành từng phiên bản. Tuy nhiên, các phần chung không thể được biên dịch thành một thư viện chia sẻ. Nó phải được biên dịch với mọi cấu hình khác nhau.

Lúc đầu, tôi tự biên dịch từng cấu hình. Chỉ mất vài giây để chuyển đổi giữa các cấu hình, và đó không phải là một nỗi đau lớn. Đến cuối dự án, một lỗi nghiêm trọng đã được phát hiện trong phần mã được chia sẻ, trong đó một thiết bị về cơ bản sẽ chiếm lấy xe buýt liên lạc. Đây là BAD! Thực sự tồi tệ. Tôi đã tìm thấy lỗi trong mã, sửa nó và biên dịch lại mọi phiên bản. Ngoại trừ một. Trong quá trình xây dựng, tôi đã bị phân tâm và quên mất một. Các tệp nhị phân đã được phát hành, máy đã được chế tạo và một ngày sau tôi nhận được một cuộc gọi điện thoại nói rằng máy ngừng phản hồi. Tôi kiểm tra nó và phát hiện ra một thiết bị đã khóa xe buýt. "Nhưng tôi đã sửa lỗi đó!".

Tôi có thể đã sửa nó, nhưng nó không bao giờ tìm được đường lên cái bảng đó. Tại sao? Bởi vì tôi không có quy trình xây dựng tự động, được xây dựng mỗi phiên bản chỉ với 1 lần nhấp.


2
Và bạn có thể đi uống cà phê trong khi kịch bản xây dựng đang chạy!
Peter Mortensen

15

Nếu tất cả những gì bạn muốn làm là <compiler> **/*.<extension>, xây dựng các kịch bản phục vụ mục đích nhỏ (mặc dù người ta có thể lập luận rằng nếu bạn thấy một Makefiledự án bạn biết bạn có thể xây dựng nó với make). Vấn đề là - các dự án không tầm thường thường đòi hỏi nhiều hơn thế - ít nhất, bạn thường sẽ cần thêm thư viện và (khi đáo hạn dự án) định cấu hình các tham số xây dựng.

IDE thường ít nhất có thể cấu hình được - nhưng bây giờ quá trình xây dựng phụ thuộc vào các tùy chọn dành riêng cho IDE. Nếu bạn đang sử dụng Eclipse , Alice thích NetBeans và Bob muốn sử dụng IntelliJ IDEA , bạn không thể chia sẻ cấu hình và khi một trong hai bạn thay đổi điều khiển nguồn, họ cần chỉnh sửa thủ công cấu hình do IDE tạo các tệp của các nhà phát triển khác hoặc thông báo cho các nhà phát triển khác để họ sẽ tự làm điều đó (điều đó có nghĩa là sẽ có các cam kết trong đó cấu hình IDE sai đối với một số IDE ...).

Bạn cũng cần tìm ra cách thực hiện thay đổi đó trong từng IDE được sử dụng bởi nhóm và nếu một trong số chúng không hỗ trợ cài đặt cụ thể đó ...

Bây giờ, vấn đề này phụ thuộc vào văn hóa - các nhà phát triển của bạn có thể thấy nó có thể chấp nhận được khi không có sự lựa chọn về IDE. Nhưng các nhà phát triển có kinh nghiệm với IDE thường vui hơn và hiệu quả hơn khi sử dụng nó và người dùng soạn thảo văn bản có xu hướng rất nhiệt tình về các công cụ yêu thích của họ, vì vậy đây là một trong những nơi bạn muốn tự do cho các nhà phát triển - và xây dựng hệ thống cho phép bạn làm điều đó. Một số người có thể có sở thích hệ thống xây dựng, nhưng nó không cuồng tín như sở thích IDE / trình soạn thảo ...

Ngay cả khi bạn khiến tất cả các nhà phát triển sử dụng cùng một IDE - chúc may mắn thuyết phục máy chủ xây dựng sử dụng nó ...

Bây giờ, đó là cho các tùy chỉnh quy trình xây dựng đơn giản, các IDE có xu hướng cung cấp GUI đẹp cho. Nếu bạn muốn các công cụ phức tạp hơn - như xử lý trước các tệp nguồn tự động tạo trước khi biên dịch - bạn thường sẽ phải viết một tập lệnh dựng sẵn trong một số vùng văn bản cơ bản bên trong cấu hình của IDE. Những hệ thống xây dựng nào bạn vẫn sẽ phải mã hóa phần đó, nhưng bạn có thể thực hiện nó trong trình soạn thảo thực tế mà bạn viết mã và quan trọng hơn: chính khung công tác của hệ thống xây dựng thường cung cấp một số hỗ trợ cho việc tổ chức các tập lệnh này.

Cuối cùng - hệ thống xây dựng tốt cho nhiều thứ hơn là chỉ xây dựng dự án - bạn có thể lập trình cho chúng thực hiện các nhiệm vụ khác mà mọi người trong nhóm có thể cần thực hiện. Ví dụ, trong Ruby on Rails , có các tác vụ hệ thống xây dựng để chạy di chuyển cơ sở dữ liệu, để làm sạch các tệp tạm thời, v.v. Việc đưa các tác vụ này vào hệ thống xây dựng đảm bảo mọi người trong nhóm có thể thực hiện chúng một cách nhất quán.


2
Đây không chỉ là vấn đề của các IDE khác nhau. Một số nhà phát triển ghét và ghét các IDE thuộc mọi loại.
David Hammen

2
@DavidHammen Tôi là một trong những nhà phát triển này (mặc dù tôi đã cấu hình Vim của mình rất khó nên có thể tôi đã biến nó thành IDE ...), và trong khi viết đoạn đó tôi đã tự động viết "trình soạn thảo" và phải sửa nó thành IDE, vì tôi nghĩ rằng vật cố trên IDE là một phần quan trọng trong câu trả lời của tôi. Người hỏi rõ ràng đến từ thế giới IDE và câu hỏi nói về sự phát triển không có IDE là điều bạn buộc phải làm khi không có IDE phù hợp. Điểm của câu trả lời này là chỉ ra cách các hệ thống xây dựng có lợi ngay cả đối với các nhóm hoàn toàn là người dùng IDE.
Idan Arye

Tôi cũng là một trong những nhà phát triển. Tôi không muốn ngón tay của mình phải rời khỏi bàn phím. Làm như vậy làm gián đoạn quá trình suy nghĩ của tôi.
David Hammen

1
Tôi không đồng ý. Tôi thích IDE. Chúng cho phép tôi không quan tâm đến tên, tra cứu dễ dàng, tái cấu trúc, giao diện người dùng đẹp. Ý tôi là, nếu một IDE có thể làm điều gì đó cho tôi mà tôi sẽ làm với sed ack, v.v., tôi sử dụng nó.
ps95

Vấn đề là với các tập lệnh xây dựng, mọi nhà phát triển trong nhóm có thể sử dụng bất cứ thứ gì họ thích, trong khi với chức năng xây dựng của IDE, bạn buộc mọi người không chỉ sử dụng IDE mà còn sử dụng IDE rất cụ thể mà dự án được định cấu hình (trừ khi bạn đã cấu hình cho nhiều IDE và chúc may mắn đồng bộ hóa điều đó ...)
Idan Arye

13

Nhiều IDE chỉ đơn giản là đóng gói các lệnh được sử dụng để xây dựng một cái gì đó và sau đó tạo ra một kịch bản và gọi nó!

Ví dụ: trong Visual Studio, bạn có thể thấy các tham số dòng lệnh cho trình biên dịch C ++ trong hộp 'dòng lệnh'. Nếu bạn nhìn kỹ vào đầu ra bản dựng, bạn sẽ thấy tệp tạm thời chứa tập lệnh xây dựng được sử dụng để chạy biên dịch.

Ngày nay, tất cả đều là MSBuild , nhưng IDE vẫn được điều hành trực tiếp.

Vì vậy, lý do bạn sử dụng dòng lệnh là vì bạn đang đi thẳng vào nguồn và bỏ qua người trung gian, một người trung gian có thể đã được cập nhật hoặc yêu cầu một đống phụ thuộc mà bạn không muốn hoặc không cần trên máy chủ không đầu. hoạt động như máy chủ tích hợp liên tục (CI) của bạn.

Ngoài ra, tập lệnh của bạn thực hiện nhiều hơn các bước định hướng dành cho nhà phát triển thông thường mà chúng được thiết kế. Ví dụ: sau khi xây dựng, bạn có thể muốn các tệp nhị phân của mình được đóng gói và sao chép vào một vị trí đặc biệt hoặc gói trình cài đặt được tạo hoặc công cụ tài liệu chạy trên chúng. Nhiều tác vụ khác nhau được thực hiện bởi một máy chủ CI vô nghĩa trên máy của nhà phát triển, vì vậy trong khi bạn có thể tạo một dự án IDE thực hiện tất cả các bước này thì bạn phải duy trì hai trong số chúng - một dự án dành cho nhà phát triển và một dự án khác để xây dựng. Một số tác vụ xây dựng (ví dụ phân tích tĩnh) có thể mất nhiều thời gian mà bạn không muốn cho các dự án dành cho nhà phát triển.

Nói tóm lại, nó chỉ dễ dàng hơn - tạo một tập lệnh để thực hiện tất cả những điều bạn muốn và thật nhanh chóng và đơn giản để khởi động nó trên dòng lệnh hoặc cấu hình máy chủ xây dựng.


9
make

dễ nhớ và gõ hơn rất nhiều

gcc -o myapp -I/include/this/dir -I/include/here/as/well -I/dont/forget/this/one src/myapp.c src/myapp.h src/things/*.c src/things/*.h

Và các dự án có thể có các lệnh biên dịch rất phức tạp. Một kịch bản xây dựng cũng có khả năng chỉ biên dịch lại những thứ đã thay đổi. Nếu bạn muốn làm một bản dựng sạch sẽ,

make clean

dễ dàng và đáng tin cậy hơn một khi được thiết lập đúng cách hơn là cố gắng ghi nhớ từng nơi một tệp trung gian có thể đã được tạo.

Tất nhiên, nếu bạn sử dụng IDE, việc nhấp vào nút xây dựng hoặc làm sạch cũng dễ dàng. Tuy nhiên, việc tự động di chuyển con trỏ đến một vị trí cụ thể trên màn hình sẽ khó khăn hơn nhiều, đặc biệt là khi vị trí đó có thể di chuyển nếu cửa sổ di chuyển, hơn là tự động hóa một lệnh văn bản đơn giản.


4

Làm thế nào khác bạn sẽ làm điều đó? Cách khác là chỉ định một lệnh dòng lệnh dài.

Một lý do khác là makefiles cho phép biên dịch gia tăng, giúp tăng tốc thời gian biên dịch lên rất nhiều.

Makefiles cũng có thể tạo một quá trình xây dựng đa nền tảng. CMake tạo các tập lệnh xây dựng khác nhau dựa trên nền tảng.

Biên tập:

Với một IDE, bạn bị ràng buộc với một cách làm việc cụ thể. Nhiều người sử dụng vim hoặc emacs mặc dù chúng không có nhiều tính năng giống IDE. Họ làm điều đó bởi vì họ muốn sức mạnh mà các biên tập viên cung cấp. Xây dựng tập lệnh là cần thiết cho những người không sử dụng IDE.

Ngay cả đối với những người sử dụng IDE, bạn có thể muốn thực sự biết điều gì đang xảy ra, do đó, tập lệnh xây dựng cung cấp cho bạn các chi tiết triển khai thấp mà cách tiếp cận GUI không có.

Bản thân IDE cũng thường xây dựng các tập lệnh bên trong; nút chạy chỉ là một cách khác để chạy lệnh make.


4

Các câu trả lời ở trên bao gồm rất nhiều nền tảng tốt, nhưng một ví dụ trong thế giới thực mà tôi muốn thêm vào (mà tôi không thể thêm dưới dạng nhận xét do không có nghiệp), là từ lập trình Android.

Tôi là một nhà phát triển chuyên nghiệp Điện thoại Android / iOS / Windows, và tôi sử dụng API dịch vụ của Google (chủ yếu là Google Maps) một nhiều .

Trong Android, các dịch vụ này yêu cầu tôi thêm kho lưu trữ khóa hoặc một loại tệp ID nhà phát triển cho Google biết tôi là chính tôi, tôi là ai, vào bảng điều khiển dành cho nhà phát triển. Nếu ứng dụng của tôi được biên dịch với kho khóa khác, phần Google Maps của ứng dụng sẽ không hoạt động.

Thay vì thêm và quản lý hàng tá kho khóa vào bảng điều khiển dành cho nhà phát triển, chỉ có một trong số đó thực sự có thể được sử dụng để cập nhật ứng dụng, tôi đưa kho khóa này vào kho bảo mật của chúng tôi và sử dụng Gradle để nói cho Android Studio biết chính xác nên sử dụng kho khóa nào khi xây dựng "gỡ lỗi" hoặc "phát hành". Bây giờ, tôi chỉ phải thêm hai kho khóa vào bảng điều khiển dành cho nhà phát triển Google của mình, một cho "gỡ lỗi" và một cho "phát hành" và tất cả các thành viên trong nhóm của tôi có thể sao chép repo và có quyền phát triển mà không cần phải vào dev điều khiển và thêm hàm băm SHA của kho khóa cụ thể của họ, hoặc tệ hơn, khiến tôi quản lý chúng. Điều này có thêm lợi ích là cung cấp cho mỗi thành viên trong nhóm một bản sao của kho khóa ký, nghĩa là nếu tôi ra khỏi văn phòng và một bản cập nhật được lên lịch, một thành viên trong nhóm chỉ cần làm theo một danh sách hướng dẫn rất ngắn để đẩy một cập nhật.

Xây dựng tự động hóa như thế này giúp xây dựng tính nhất quán và giảm nợ kỹ thuật bằng cách giảm thời gian thiết lập khi chúng tôi có nhà phát triển mới, máy mới hoặc khi chúng tôi phải tạo lại hình ảnh cho máy.


1

Xây dựng lợi thế kịch bản:

  • các thay đổi trông giống như mã (ví dụ: trong lệnh git diff), không giống như các tùy chọn được kiểm tra khác nhau trong hộp thoại

  • tạo ra nhiều đầu ra hơn là một bản dựng đơn giản

Trong một số dự án trước đây của tôi, tôi đã sử dụng các tập lệnh xây dựng để:

  • tạo tài liệu dự án (dựa trên doxygen)
  • xây dựng
  • chạy thử nghiệm đơn vị
  • tạo báo cáo bảo hiểm thử nghiệm đơn vị
  • đóng gói nhị phân vào một kho lưu trữ phát hành
  • tạo ghi chú phát hành nội bộ (dựa trên thông báo "git log")
  • kiểm tra tự động

0

Thường thì bạn có thể gọi nút "xây dựng" theo cách tự động (chẳng hạn như Visual Studio chấp nhận đối số dòng lệnh). Mọi người viết các tập lệnh xây dựng ngay khi họ cần một cái gì đó mà nút xây dựng không thể cung cấp.

Ví dụ, hầu hết các IDE sẽ chỉ cho phép bạn xây dựng một nền tảng tại một thời điểm. Hoặc chỉ một ngôn ngữ tại một thời điểm. Sau đó, bạn sẽ làm gì với các đầu ra được xây dựng: IDE của bạn có thể cuộn tất cả chúng thành một gói cài đặt không?

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.