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

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

Để làm được điều này, công cụ này sẽ chạy trên kết quả giai đoạn phân tích của Bazel, trong đó tích hợp các hiệu ứng này. Ngược lại, query chạy trên kết quả của giai đoạn tải của Bazel trước khi đánh giá các tuỳ 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 chứa mục tiêu.

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

Cú pháp cơ bản

Lệnh gọi cquery đơn giản sẽ có dạng như sau:

bazel cquery "function(//target)"

Biểu thức truy vấn "function(//target)" bao gồm:

  • function(...) là hàm để chạy trên mục tiêu. cquery hỗ trợ hầu hết các hàm của query, cùng với một số hàm mới.
  • //target là biểu thức được đưa 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 về truy vấn để biết ví dụ.

cquery yêu cầu mục tiêu chạy qua giai đoạn tải và phân tích. Trừ phi 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. Xem --universe_scope để truy vấn các phần phụ thuộc của mục tiêu bản dựng cấp cao nhất.

Cấu hình

Dòng:

//tree:ash (9f87702)

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

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

$ bazel config 9f87702

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

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

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

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

Đố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 đã định cấu hình bằng một nhãn khớp với mẫu đó. Kết quả đầu ra là có tính quyết định, nhưng cquery không đảm bảo thứ tự ngoài hợp đồng thứ tự truy vấn chính.

Điều này dẫn đến kết quả tinh vi hơn cho biểu thức truy vấn so với query. Ví dụ: những nội dung 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.

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

Hàm

Trong tập hợp các hàmquery hỗ trợ, cquery hỗ trợ tất cả các hàm trừ visible, siblings, buildfilestests.

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

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ố đầu tiên và cấu hình 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 hàm băm cấu hình tuỳ chỉnh. Bạn có thể truy xuất hàm băm 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 mọi kết quả của đối số đầu tiên trong cấu hình đã chỉ định, thì hệ thống sẽ chỉ trả về những kết quả có thể tìm thấy. Nếu không 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

Tuỳ chọn xây dựng

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

Sử dụng các tuỳ chọn cquery

--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 đã định cấu hình sẽ trải qua các quá trình chuyển đổi, khiến cấu hình của các mục tiêu đó 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ể mục tiêu đó đượ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",
)

Quy tắc tạo sinh đị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 kết quả sau:

Truy vấn Đã tạo mục tiêu Đầu ra
bazel cquery "//x:tool" //x:tool //x:tool(targetconfig)
bazel cquery "//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 thuộc tính này, tất cả các mục tiêu đề 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 sẽ được dùng làm tập hợp của truy vấn. Dù bằng cách nào, các mục tiêu được tạo phải có thể tạo ở cấp cao nhất (nghĩa là tương thích với các tuỳ chọn cấp cao nhất). cquery trả về kết quả đóng bắc cầu các mục tiêu cấp cao nhất này.

Ngay cả khi bạn 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ì bạn cũng nên làm như vậy. Ví dụ: việc thiết lập --universe_scope một cách rõ ràng có thể ngă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. Bạn cũng có thể chỉ định phiên bản cấu hình của mục tiêu đang tìm kiếm (vì hiện tại bạn không thể chỉ định đầy đủ điều này bằng bất kỳ cách nào khác). 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, default=True)

Việc đặt cờ này thành false sẽ lọc ra mọi 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. Quá trình này bao gồm cả việc lọc các chuỗi công cụ đã phân giải.

--tool_deps (boolean, default=True)

Việc đặt cờ này thành false sẽ lọc ra tất cả mục tiêu đã đị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 đó vượt qua quá trình 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, việc đặt --notool_deps cũng sẽ chỉ trả về các 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ụ đã được phân giải.

--include_aspects (boolean, default=True)

Các khung hì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, việc này sử dụng nhiều bộ nhớ hơn. Tuy nhiên, việc làm theo chúng sẽ mang lại kết quả chính xác hơn.

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

Nếu truy vấn các khía cạnh bị vô hiệu hoá, bạn có thể gặp sự cố mục tiêu X không thành công trong khi xây dựng 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ì sự phụ thuộc xảy ra thông qua một khía cạnh.

Định dạng đầu ra

Theo mặc định, cquery sẽ xuất ra danh sách các cặp nhãn và cấu hình theo thứ tự phụ thuộc. Bạn cũng có thể hiển thị kết quả theo các cách khác.

Kiểu chuyển cảnh

--transitions=lite
--transitions=full

Hoạt động 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ột mục tiêu có thể áp dụng chuyển đổi cho cấu hình máy chủ lưu trữ trên tất cả các phần phụ thuộc trong thuộc tính tools của nó. Những hành động này được gọi là chuyển đổi thuộc tính. Các quy tắc cũng có thể áp dụng hiệu ứng chuyển đổi trên cấu hình riêng của chúng, được gọi là chuyển đổi lớp quy tắc. Định dạng đầu ra này sẽ 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à tác động của chúng đối với các tuỳ chọn xây dựng.

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

--output=proto

Tuỳ chọn này khiến các mục tiêu kết quả được in ở dạng vùng đệ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 thông báo ConfiguredTarget và danh sách thông báo Configuration. Mỗi ConfiguredTarget có một configuration_id có giá trị bằng giá trị của trường id trong thông báo Configuration tương ứng.

--[no]proto:include_configurations

Theo mặc định, kết quả cquery 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.

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.

Biểu đồ đầu ra

--output=graph

Tuỳ chọn này tạo đầu ra 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ả của biểu đồ của query để biết thông tin chi tiết. cquery cũng hỗ trợ --graph:node_limit--graph:factored.

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

Phương ngữ Starlark "cquery"

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

build_options(target)

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

Nếu mục tiêu là tệp đầu vào, build_options(target) sẽ trả về Không có (None), 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 tệp ánh xạ có khoá là tên của nhà cung cấp (ví dụ: "DefaultInfo") và có giá trị là giá trị Starlark. Những nhà cung cấp có giá trị không phải là giá trị Starlark hợp pháp sẽ bị loại khỏi bản đồ này.

Ví dụ

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

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

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

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

In danh sách ghi nhớ của tất cả hành động do //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 //baz của cc_library.

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

In giá trị của tuỳ 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 chỉ có một kết quả đầ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 hoàn toàn 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ừ 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

cquery so với truy vấn

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

  • cquery tuân theo các nhánh select() cụ thể để lập mô hình biểu đồ chính xác mà bạn tạo. query không biết bản dựng chọn nhánh nào, vì vậy, sẽ có thể ước tính bằng cách đưa vào tất cả các nhánh.
  • Độ chính xác của cquery yêu cầu tạo biểu đồ nhiều hơn 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. Việc này tốn nhiều thời gian hơn và tốn nhiều bộ nhớ hơn.
  • Cách diễn giải của cquery về ngôn ngữ truy vấn sẽ gây 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ể giúp bạn làm việc này.
  • Là một công cụ mới hơn, cquery không hỗ trợ một số trường hợp sử dụng nhất định. 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ả các mục tiêu do cquery "tạo" phải có cùng một cấu hình.

Trước khi đánh giá các truy vấn, cquery sẽ kích hoạt một bản dựng ngay trước thời điểm mà các hành động tạo bản dựng sẽ thực thi. Theo mặc định, các mục tiêu mà nó "tạo" được chọn từ tất cả các nhãn xuất hiện trong biểu thức truy vấn (bạn có thể ghi đè lựa chọn này 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 quy tắc này thường có chung 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 chuyển đổi cạnh sắp diễn ra. Đây là nơi cquery thiếu sót.

Giải pháp: Nếu có thể, hãy đặt --universe_scope ở 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ợ --output=xml.

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

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

Bạn có thể xem những tác động kéo dài của 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ỳ tuỳ 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 đệ quy (/...)

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 gợi ý không chính xác gói //foo không thuộc phạm vi của gói mặc dù --universe_scope=//foo:app bao gồm gói đó. Điều này là do các giới hạn về thiết kế trong cquery. Để giải quyết vấn đề này, hãy đưa //foo/... vào phạm vi vũ trụ một cách rõ ràng:

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

Nếu cách này không hiệu quả (ví dụ: vì 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 cấu thành của nó bằng một truy vấn xử lý trước:

# 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 "+" -))"