Truy vấn có thể định cấu hình (truy vấn)

Sử dụng bộ sưu tập để sắp xếp ngăn nắp các trang Lưu và phân loại nội dung dựa trên lựa chọn ưu tiên của bạn.
Báo cáo sự cố Xem nguồn

cquery là một biến thể của query, giúp xử lý chính xác select() và hiệu ứng của các tuỳ chọn bản dựng trên biểu đồ bản dựng.

Tính năng này đạt được điều này bằng cách chạy qua kết quả của giai đoạn phân tích của Bazel, trong đó tích hợp các hiệu ứng này. Theo thời gian, query sẽ chạy qua các kết quả của giai đoạn tải của Bielel trước khi đánh giá các lựa chọn.

Ví dụ:

$ cat > tree/BUILD <<EOF
sh_library(
    name = "ash",
    deps = select({
        ":excelsior": [":manna-ash"],
        ":americana": [":white-ash"],
        "//conditions:default": [":common-ash"],
    }),
)
sh_library(name = "manna-ash")
sh_library(name = "white-ash")
sh_library(name = "common-ash")
config_setting(
    name = "excelsior",
    values = {"define": "species=excelsior"},
)
config_setting(
    name = "americana",
    values = {"define": "species=americana"},
)
EOF
# Traditional query: query doesn't know which select() branch you will choose,
# so it conservatively lists all of possible choices, including all used config_settings.
$ bazel query "deps(//tree:ash)" --noimplicit_deps
//tree:americana
//tree:ash
//tree:common-ash
//tree:excelsior
//tree:manna-ash
//tree:white-ash

# cquery: cquery lets you set build options at the command line and chooses
# the exact dependencies that implies (and also the config_setting targets).
$ bazel cquery "deps(//tree:ash)" --define species=excelsior --noimplicit_deps
//tree:ash (9f87702)
//tree:manna-ash (9f87702)
//tree:americana (9f87702)
//tree:excelsior (9f87702)

Mỗi kết quả bao gồm một giá trị nhận dạng duy nhất (9f87702) của cấu hình mục tiêu xây dựng.

cquery chạy trên biểu đồ mục tiêu đã định cấu hình, nên biểu đồ này không có thông tin chi tiết về các cấu phần phần mềm như hành động xây dựng cũng như quyền truy cập vào các quy tắc [test_suite](/reference/be/general#test_suite) vì chúng không phải là mục tiêu được định cấu hình. Để xem ví dụ trước, hãy xem [aquery](/docs/aquery).

Cú pháp cơ bản

Một lệnh gọi cquery đơn giản có dạng như sau:

bazel cquery "function(//target)"

Biểu thức truy vấn "function(//target)" bao gồm những nội dung sau:

  • function(...) là hàm chạy trên mục tiêu. cquery hỗ trợ hầu hết các chức năng của query, cộng với một số hàm mới.
  • //target là biểu thức được cung cấp cho hàm. Trong ví dụ này, biểu thức là một mục tiêu đơn giản. Tuy nhiên, ngôn ngữ truy vấn cũng cho phép lồng các hàm. Xem Hướng dẫn truy vấn để biết ví dụ.

cquery yêu cầu một mục tiêu để chạy các giai đoạn tải và phân tích. Trừ khi có quy định khác, cquery sẽ phân tích cú pháp(các) mục tiêu được liệt kê trong biểu thức truy vấn. Hãy xem --universe_scope để biết các phần phụ thuộc của mục tiêu bản dựng cấp cao nhất.

Phiên bản

Dòng:

//tree:ash (9f87702)

có nghĩa là //tree:ash được xây dựng trong cấu hình có mã nhận dạng 9f87702. Đối với hầu hết các mục tiêu, đây là hàm băm mờ của các giá trị tùy chọn bản dựng xác định cấu hình.

Để xem toàn bộ nội dung của cấu hình, hãy chạy:

$ bazel config 9f87702

Cấu hình máy chủ lưu trữ sử dụng mã nhận dạng đặc biệt (HOST). Tệp nguồn không được tạo (như các tệp thường tìm thấy trong srcs) sử dụng mã nhận dạng đặc biệt (null) (vì các tệp này không cần phải được định cấu hình).

9f87702 là tiền tố của mã nhận dạng hoàn chỉnh. Lý do là mã nhận dạng hoàn chỉnh là các hàm băm SHA-256 dài và khó theo dõi. cquery hiểu mọi tiền tố hợp lệ của một mã hoàn chỉnh, tương tự như các hàm băm ngắn. Để xem mã nhận dạng đầy đủ, hãy chạy $ bazel config.

Đánh giá mẫu mục tiêu

//foo có ý nghĩa khác với cquery so với query. Điều này là do cquery đánh giá các mục tiêu đã định cấu hình và biểu đồ bản dựng có thể có nhiều phiên bản được định cấu hình của //foo.

Đối với cquery, mẫu mục tiêu trong biểu thức truy vấn sẽ đánh giá mỗi mục tiêu được định cấu hình bằng một nhãn khớp với mẫu đó. Kết quả đầu ra là xác định, nhưng cquery không đảm bảo thứ tự ngoài hợp đồng đặt hàng truy vấn cốt lõi.

Điều này tạo ra kết quả tinh tế hơn cho các biểu thức truy vấn so với query. Ví dụ: những điều sau đây có thể tạo ra nhiều kết quả:

# Analyzes //foo in the target configuration, but also analyzes
# //genrule_with_foo_as_tool which depends on a host-configured
# //foo. So there are two configured target instances of //foo in
# the build graph.
$ bazel cquery //foo --universe_scope=//foo,//genrule_with_foo_as_tool
//foo (9f87702)
//foo (HOST)

Nếu bạn muốn khai báo chính xác thực thể nào cần truy vấn, hãy sử dụng hàm config.

Xem tài liệu về mẫu mục tiêu của query để biết thêm thông tin về mẫu mục tiêu.

Hàm

Trong nhóm hàm được query hỗ trợ, cquery hỗ trợ tất cả trừ visible, siblings, buildfilestests.

cquery cũng ra mắt các hàm mới sau đây:

cấu hình

expr ::= config(expr, word)

Toán tử config cố gắng tìm mục tiêu đã định cấu hình cho nhãn được biểu thị bằng đối số và cấu hình đầu tiên do đối số thứ hai chỉ định.

Các giá trị hợp lệ cho đối số thứ hai là target, host, null hoặc một hàm băm cấu hình tuỳ chỉnh. Hàm băm có thể được truy xuất từ $ bazel config hoặc dữ liệu đầu ra của cquery trước.

Ví dụ:

$ bazel cquery "config(//bar, host)" --universe_scope=//foo
$ bazel cquery "deps(//foo)"
//bar (HOST)
//baz (3732cc8)

$ bazel cquery "config(//baz, 3732cc8)"

Nếu không thể tìm thấy tất cả kết quả của đối số đầu tiên trong cấu hình đã chỉ định, thì chỉ những kết quả có thể được tìm thấy mới được trả về. Nếu không thể tìm thấy kết quả nào trong cấu hình đã chỉ định, thì truy vấn sẽ không thành công.

Tùy chọn

Xây dựng các lựa chọn

cquery chạy trên một bản dựng Bazel thông thường và do đó kế thừa tập hợp các tuỳ chọn có sẵn trong một bản dựng.

Sử dụng các tuỳ chọn truy vấn

--universe_scope (danh sách được phân tách bằng dấu phẩy)

Thông thường, các phần phụ thuộc của mục tiêu được định cấu hình sẽ trải qua các lượt chuyển đổi, khiến cấu hình của các bộ lọc khác với phần phụ thuộc. Cờ này cho phép bạn truy vấn một mục tiêu như thể nó được tạo dưới dạng phần phụ thuộc hoặc phần phụ thuộc bắc cầu của một mục tiêu khác. Ví dụ:

# x/BUILD
genrule(
     name = "my_gen",
     srcs = ["x.in"],
     outs = ["x.cc"],
     cmd = "$(locations :tool) $< >$@",
     tools = [":tool"],
)
cc_library(
    name = "tool",
)

Genrule định cấu hình các công cụ của chúng trong cấu hình máy chủ để các truy vấn sau đây sẽ tạo ra các kết quả sau:

Truy vấn Mục tiêu đã tạo Đầu ra
truy vấn bazel "//x:tool" //x:tool //x:tool(targetconfig)
truy vấn bazel "//x:tool" --universe_scope\"x:my_gen" //x:my_gen //x:tool(hostconfig)

Nếu bạn đặt cờ này thì nội dung của cờ sẽ được tạo. Nếu bạn không đặt chính sách này, tất cả các mục tiêu được đề cập trong biểu thức truy vấn sẽ được tạo. Việc đóng bắc cầu của các mục tiêu đã tạo được dùng làm vũ trụ của truy vấn. Dù bằng cách nào, mục tiêu cần tạo phải có thể tạo được ở cấp cao nhất (nghĩa là tương thích với các tùy chọn cấp cao nhất). cquery trả về kết quả là các mục tiêu cấp cao nhất đã đóng về bắc cầu.

Ngay cả khi có thể tạo tất cả các mục tiêu trong một biểu thức truy vấn ở cấp cao nhất, thì việc làm như vậy vẫn có thể hưởng lợi. Ví dụ: việc đặt rõ ràng --universe_scope có thể ngăn chặn việc tạo mục tiêu nhiều lần trong các cấu hình mà bạn không quan tâm. Điều này cũng có thể giúp xác định phiên bản cấu hình của mục tiêu mà bạn đang tìm kiếm (vì hiện tại bạn không thể chỉ định đầy đủ cách này). Bạn nên đặt cờ này nếu biểu thức truy vấn của bạn phức tạp hơn deps(//foo).

--implicit_deps (boolean, mặc định=True)

Việc đặt cờ này thành false sẽ lọc ra tất cả kết quả không được đặt rõ ràng trong tệp BUILD và thay vào đó được Bazel đặt ở nơi khác. Bao gồm cả việc lọc các chuỗi công cụ đã phân giải.

--tool_deps (boolean, mặc định=True)

Việc đặt cờ này thành false sẽ lọc ra tất cả mục tiêu đã được định cấu hình mà đường dẫn từ mục tiêu được truy vấn đến các mục tiêu đó sẽ chuyển đổi giữa cấu hình mục tiêu và cấu hình không phải mục tiêu. Nếu mục tiêu được truy vấn nằm trong cấu hình mục tiêu, thì việc đặt --notool_deps sẽ chỉ trả về các mục tiêu cũng có trong cấu hình mục tiêu. Nếu mục tiêu được truy vấn nằm trong cấu hình không phải mục tiêu, thì việc đặt --notool_deps sẽ chỉ trả về mục tiêu trong cấu hình không phải mục tiêu. Chế độ cài đặt này thường không ảnh hưởng đến việc lọc các chuỗi công cụ đã phân giải.

--include_aspects (boolean, mặc định=True)

Các khía cạnh có thể thêm các phần phụ thuộc bổ sung vào một bản dựng. Theo mặc định, cquery không tuân theo các khía cạnh vì chúng làm cho biểu đồ có thể truy vấn lớn hơn và sử dụng nhiều bộ nhớ hơn. Nhưng việc làm theo các kết quả này sẽ tạo ra kết quả chính xác hơn.

Nếu bạn không lo lắng về tác động của bộ nhớ đối với các truy vấn lớn, hãy bật cờ này theo mặc định trong bazelrc.

Nếu truy vấn các thành phần bị vô hiệu hoá, bạn có thể gặp vấn đề khi mục tiêu X bị lỗi trong khi tạo mục tiêu Y nhưng cquery somepath(Y, X)cquery deps(Y) | grep 'X' không trả về kết quả nào vì phần phụ thuộc xảy ra thông qua một thành phần.

Định dạng đầu ra

Theo mặc định, truy vấn xuất ra sẽ dẫn đến danh sách các cặp nhãn và cấu hình được sắp xếp theo phần phụ thuộc. Ngoài ra, còn có các lựa chọn khác để tiết lộ kết quả.

Chuyển đổi

--transitions=lite
--transitions=full

Chuyển đổi cấu hình được dùng để tạo mục tiêu bên dưới mục tiêu cấp cao nhất trong các cấu hình khác với mục tiêu cấp cao nhất.

Ví dụ: mục tiêu có thể áp dụng việc chuyển đổi sang cấu hình máy chủ lưu trữ trên tất cả các phần phụ thuộc của thuộc tính tools. Đây được gọi là các lượt chuyển đổi thuộc tính. Các quy tắc cũng có thể áp dụng chuyển đổi trên cấu hình của riêng chúng, được gọi là chuyển đổi lớp quy tắc. Định dạng đầu ra này xuất ra thông tin về các quá trình chuyển đổi này, chẳng hạn như loại chuyển đổi và ảnh hưởng của chúng đến các tuỳ chọn bản dựng.

Định dạng đầu ra này được kích hoạt bởi cờ --transitions theo mặc định, được đặt thành NONE. Bạn có thể đặt chế độ này thành chế độ FULL hoặc LITE. Chế độ FULL sẽ xuất thông tin về các chuyển đổi lớp quy tắc và các chuyển đổi thuộc tính, bao gồm cả một số khác biệt về tùy chọn trước và sau khi chuyển đổi. Chế độ LITE sẽ xuất ra cùng một thông tin mà không có sự khác biệt về tuỳ chọn.

Đầu ra thông báo của giao thức

--output=proto

Tuỳ chọn này làm cho các mục tiêu kết quả được in dưới dạng bộ đệm giao thức nhị phân. Bạn có thể xem định nghĩa về vùng đệm giao thức tại src/main/protobuf/Analysis.proto.

CqueryResult là thông báo cấp cao nhất chứa kết quả của truy vấn. Ứng dụng này có danh sách các tin nhắn ConfiguredTarget và danh sách tin nhắn Configuration. Mỗi ConfiguredTarget có một configuration_id với giá trị bằng với giá trị của trường id trong thông báo Configuration tương ứng.

--[no]proto:include_configures

Theo mặc định, kết quả truy vấn sẽ trả về thông tin cấu hình như một phần của từng mục tiêu được định cấu hình. Nếu bạn muốn bỏ qua thông tin này và nhận đầu ra proto được định dạng chính xác như đầu ra proto của truy vấn, hãy đặt cờ này thành false.

Hãy xem tài liệu về đầu ra proto của truy vấn để biết thêm các tuỳ chọn liên quan đến đầu ra proto.

Xuất biểu đồ

--output=graph

Tuỳ chọn này tạo ra kết quả dưới dạng tệp .dot tương thích với Graphviz. Hãy xem tài liệu về kết quả biểu đồ của query để biết thông tin chi tiết. cquery cũng hỗ trợ --graph:node_limit--graph:factored.

Xuất tệp

--output=files

Tuỳ chọn này in danh sách các tệp đầu ra do mỗi mục tiêu tạo ra, khớp với truy vấn tương tự như danh sách được in ở cuối lệnh gọi bazel build. Kết quả chỉ chứa các tệp được quảng cáo trong các nhóm đầu ra được yêu cầu do cờ --output_groups xác định. Phần này có cả các tệp nguồn.

Xác định định dạng đầu ra bằng Starlark

--output=starlark

Định dạng đầu ra này gọi một hàm Starlark cho từng mục tiêu đã định cấu hình trong kết quả truy vấn, và in giá trị mà lệnh gọi trả về. Cờ --starlark:file chỉ định vị trí của tệp Starlark xác định một hàm có tên là format bằng một tham số duy nhất là target. Hàm này được gọi cho mỗi Target (Đích) trong kết quả truy vấn. Ngoài ra, để thuận tiện, bạn có thể chỉ định phần nội dung chỉ được khai báo là def format(target): return expr bằng cách sử dụng cờ --starlark:expr.

'cquery' phương ngữ Starlark

Môi trường truy vấn Starlark khác với tệp BUILD hoặc .bzl. Tính năng này bao gồm tất cả hằng số và hàm tích hợp sẵn của Starlark, cùng với một số hằng số cụ thể theo truy vấn được mô tả bên dưới, nhưng không bao gồm (ví dụ) glob, native hoặc rule và không hỗ trợ câu lệnh tải.

build_options(mục tiêu)

build_options(target) trả về một bản đồ có khoá là giá trị nhận dạng tuỳ chọn bản dựng (xem Cấu hình) và có giá trị là giá trị Starlark. Các tuỳ chọn bản dựng có giá trị không phải là giá trị Starlark hợp pháp sẽ bị bỏ qua khỏi bản đồ này.

Nếu mục tiêu là tệp đầu vào, thì build_options(target) sẽ trả về Không có, vì các mục tiêu tệp đầu vào có cấu hình rỗng.

nhà cung cấp(mục tiêu)

providers(target) trả về một bản đồ có khoá là tên của nhà cung cấp (ví dụ: "DefaultInfo") và có giá trị là giá trị Starlark. Các nhà cung cấp có giá trị không phải là giá trị Starlark hợp pháp sẽ bị bỏ qua trên bản đồ này.

Ví dụ

In danh sách được phân tách bằng dấu cách gồm các tên cơ sở của tất cả các tệp do //foo tạo:

  bazel cquery //foo --output=starlark \
    --starlark:expr="' '.join([f.basename for f in target.files.to_list()])"

In danh sách các đường dẫn được phân tách bằng dấu cách của tất cả các tệp do các mục tiêu quy tắc tạo ra trong //bar và các gói con:

  bazel cquery 'kind(rule, //bar/...)' --output=starlark \
    --starlark:expr="' '.join([f.path for f in target.files.to_list()])"

In danh sách nội dung gợi nhớ của tất cả các thao tác đã được //foo đăng ký.

  bazel cquery //foo --output=starlark \
    --starlark:expr="[a.mnemonic for a in target.actions]"

In danh sách các kết quả biên dịch được đăng ký bởi cc_library //baz.

  bazel cquery //baz --output=starlark \
    --starlark:expr="[f.path for f in target.output_groups.compilation_outputs.to_list()]"

In giá trị của tùy chọn dòng lệnh --javacopt khi tạo //foo.

  bazel cquery //foo --output=starlark \
    --starlark:expr="build_options(target)['//command_line_option:javacopt']"

In nhãn của từng mục tiêu với duy nhất một đầu ra. Ví dụ này sử dụng các hàm Starlark được xác định trong một tệp.

  $ cat example.cquery

  def has_one_output(target):
    return len(target.files.to_list()) == 1

  def format(target):
    if has_one_output(target):
      return target.label
    else:
      return ""

  $ bazel cquery //baz --output=starlark --starlark:file=example.cquery

In nhãn của từng mục tiêu đúng là Python 3. Ví dụ này sử dụng các hàm Starlark được xác định trong một tệp.

  $ cat example.cquery

  def format(target):
    p = providers(target)
    py_info = p.get("PyInfo")
    if py_info and py_info.has_py3_only_sources:
      return target.label
    else:
      return ""

  $ bazel cquery //baz --output=starlark --starlark:file=example.cquery

Trích xuất giá trị từ một Nhà cung cấp do người dùng xác định.

  $ cat some_package/my_rule.bzl

  MyRuleInfo = provider(fields={"color": "the name of a color"})

  def _my_rule_impl(ctx):
      ...
      return [MyRuleInfo(color="red")]

  my_rule = rule(
      implementation = _my_rule_impl,
      attrs = {...},
  )

  $ cat example.cquery

  def format(target):
    p = providers(target)
    my_rule_info = p.get("//some_package:my_rule.bzl%MyRuleInfo'")
    if my_rule_info:
      return my_rule_info.color
    return ""

  $ bazel cquery //baz --output=starlark --starlark:file=example.cquery

truy vấn cquery so với truy vấn

cqueryquery bổ trợ cho nhau và vượt trội trong các lĩnh vực khác. Hãy cân nhắc những điều sau để quyết định phương án phù hợp với bạn:

  • cquery tuân theo các nhánh select() cụ thể để tạo mô hình biểu đồ chính xác mà bạn xây dựng. query không biết bản dựng nào được chọn, vì vậy, hãy so sánh quá mức bằng cách bao gồm tất cả các nhánh.
  • Độ chính xác của cquery yêu cầu tạo nhiều biểu đồ hơn so với query. Cụ thể, cquery đánh giá mục tiêu được định cấu hình trong khi query chỉ đánh giá mục tiêu. Như vậy sẽ tốn nhiều thời gian hơn và sử dụng nhiều bộ nhớ hơn.
  • Sự hiểu biết của cquery về ngôn ngữ truy vấn tạo ra sự không rõ ràng mà query tránh được. Ví dụ: nếu "//foo" tồn tại trong hai cấu hình, thì cquery "deps(//foo)" nên sử dụng cấu hình nào? Hàm [config](#config) có thể trợ giúp bạn.
  • Là một công cụ mới, cquery không hỗ trợ cho một số trường hợp sử dụng. Hãy xem phần Các vấn đề đã biết để biết thông tin chi tiết.

Vấn đề đã biết

Tất cả mục tiêu mà cquery "bản dựng" phải có cùng cấu hình.

Trước khi đánh giá các truy vấn, cquery sẽ kích hoạt bản dựng ngay trước thời điểm hành động tạo bản dựng thực thi. Theo mặc định, các mục tiêu mà "bản dựng" đó được chọn từ tất cả các nhãn xuất hiện trong biểu thức truy vấn (điều này có thể bị ghi đè bằng --universe_scope). Các mục tiêu này phải có cùng cấu hình.

Mặc dù các cấu hình này thường có cùng cấu hình "mục tiêu" cấp cao nhất, nhưng các quy tắc có thể thay đổi cấu hình của riêng chúng bằng các lượt chuyển đổi cạnh sắp tới. Đây là nơi mà cquery bị thiếu.

Cách giải quyết: Nếu có thể, hãy đặt --universe_scope thành một phạm vi nghiêm ngặt hơn. Ví dụ:

# This command attempts to build the transitive closures of both //foo and
# //bar. //bar uses an incoming edge transition to change its --cpu flag.
$ bazel cquery 'somepath(//foo, //bar)'
ERROR: Error doing post analysis query: Top-level targets //foo and //bar
have different configurations (top-level targets with different
configurations is not supported)

# This command only builds the transitive closure of //foo, under which
# //bar should exist in the correct configuration.
$ bazel cquery 'somepath(//foo, //bar)' --universe_scope=//foo

Không hỗ trợ cho --output=xml.

Kết quả không xác định.

cquery không tự động xóa biểu đồ bản dựng khỏi các lệnh trước đó và do đó dễ dàng nhận kết quả từ các truy vấn trước đây. Ví dụ: genquery thực hiện việc chuyển đổi máy chủ trên thuộc tính tools – tức là chuyển đổi các công cụ trong cấu hình máy chủ.

Bạn có thể xem những hiệu ứng kéo dài của việc chuyển đổi đó ở bên dưới.

$ cat > foo/BUILD <<<EOF
genrule(
    name = "my_gen",
    srcs = ["x.in"],
    outs = ["x.cc"],
    cmd = "$(locations :tool) $< >$@",
    tools = [":tool"],
)
cc_library(
    name = "tool",
)
EOF

    $ bazel cquery "//foo:tool"
tool(target_config)

    $ bazel cquery "deps(//foo:my_gen)"
my_gen (target_config)
tool (host_config)
...

    $ bazel cquery "//foo:tool"
tool(host_config)

Giải pháp: thay đổi bất kỳ tùy chọn khởi động nào để buộc phân tích lại các mục tiêu đã định cấu hình. Ví dụ: thêm --test_arg=&lt;whatever&gt; vào lệnh tạo bản dựng.

Khắc phục sự cố

Mẫu mục tiêu định kỳ (/...)

Nếu bạn gặp phải:

$ bazel cquery --universe_scope=//foo:app "somepath(//foo:app, //foo/...)"
ERROR: Error doing post analysis query: Evaluation failed: Unable to load package '[foo]'
because package is not in scope. Check that all target patterns in query expression are within the
--universe_scope of this query.

Điều này cho thấy không chính xác rằng gói //foo không thuộc phạm vi mặc dù --universe_scope=//foo:app bao gồm cả gói. Điều này là do giới hạn thiết kế trong cquery. Để khắc phục, hãy đưa //foo/... vào phạm vi cụ thể:

$ bazel cquery --universe_scope=//foo:app,//foo/... "somepath(//foo:app, //foo/...)"

Nếu cách đó không hiệu quả (ví dụ: do một số mục tiêu trong //foo/... không thể tạo bản dựng bằng các cờ bản dựng đã chọn), hãy mở gói mẫu theo cách thủ công vào các gói phù hợp với truy vấn trước khi xử lý:

# Replace "//foo/..." with a subshell query call (not cquery!) outputting each package, piped into
# a sed call converting "<pkg>" to "//<pkg>:*", piped into a "+"-delimited line merge.
# Output looks like "//foo:*+//foo/bar:*+//foo/baz".
#
$  bazel cquery --universe_scope=//foo:app "somepath(//foo:app, $(bazel query //foo/...
--output=package | sed -e 's/^/\/\//' -e 's/$/:*/' | paste -sd "+" -))"