diff --git a/.circleci/config.yml b/.circleci/config.yml
index 6b9b39eb2..27068008b 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -125,7 +125,7 @@ jobs:
working_directory: ~/please
macos:
xcode: "16.4.0"
- resource_class: macos.m1.medium.gen1
+ resource_class: m4pro.medium
steps:
- checkout
- attach_workspace:
@@ -206,7 +206,7 @@ jobs:
build-darwin:
macos:
xcode: "16.4.0"
- resource_class: macos.m1.medium.gen1
+ resource_class: m4pro.medium
environment:
PLZ_ARGS: "--profile ci --exclude pip --exclude embed"
steps:
diff --git a/.cirrus.yml b/.cirrus.yml
index 4129e2efb..e7146207b 100644
--- a/.cirrus.yml
+++ b/.cirrus.yml
@@ -1,5 +1,5 @@
freebsd_instance:
- image_family: freebsd-14-2
+ image_family: freebsd-14-3
env:
GOPROXY: https://proxy.golang.org
diff --git a/ChangeLog b/ChangeLog
index e892077bd..f1f233b62 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,37 @@
+Version 17.27.0
+---------------
+ * Traverse `data` and `debug_data` for run-time dependencies (#3469)
+
+Version 17.26.0
+---------------
+ * Optionally traverse `srcs` and `deps` for run-time dependencies (#3466)
+
+Version 17.25.1
+---------------
+ * Bump arcat to v1.3.1, fixing a bug relating to symbol table stripping on Darwin and FreeBSD (#3462)
+
+Version 17.25.0
+---------------
+ * Add `maxdepth` parameter to `get_labels` built-in, allowing for recursive dependency searches to be depth-limited (#3460)
+
+Version 17.24.2
+---------------
+ * Improve platform detection logic in `plz init`'s `pleasew` script (#3458)
+
+Version 17.24.1
+---------------
+ * Download run-time dependencies when running a target remotely (#3453)
+
+Version 17.24.0
+---------------
+ * Define `PLZ_ENV` environment variable in build environments (#3444)
+ * Strip symbols from please_sandbox release binaries (#3450)
+ * Implement run-time dependencies for binary targets via the `runtime_deps` parameter (#3451)
+
+Version 17.23.0
+---------------
+ * Bump arcat to v1.3.0, adding the `--include` option to the `zip` command (#3441)
+
Version 17.22.0
---------------
* Add `--audit_log_dir` option for logging of Please invocations, build commands and remote file fetching (#3425)
diff --git a/VERSION b/VERSION
index 721d4f737..b1799abc7 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-17.22.0
+17.27.0
diff --git a/docs/BUILD b/docs/BUILD
index 8cf72d2c1..5e1937f50 100644
--- a/docs/BUILD
+++ b/docs/BUILD
@@ -60,7 +60,7 @@ plugins = {
"python": "v1.14.0",
"java": "v0.4.5",
"go": "v1.26.0",
- "cc": "v0.5.2",
+ "cc": "v0.7.1",
"shell": "v0.2.0",
"go-proto": "v0.3.0",
"python-proto": "v0.1.0",
diff --git a/docs/codelabs/python_intro.md b/docs/codelabs/python_intro.md
index b438e33c2..bb1d1882b 100644
--- a/docs/codelabs/python_intro.md
+++ b/docs/codelabs/python_intro.md
@@ -349,7 +349,3 @@ determine files changes since master, watch rules and build them automatically a
`plz help`, and explore this rich set of commands!
Otherwise, why not try one of the other codelabs!
-, watch rules and build them automatically as things change and much more! Use
-`plz help`, and explore this rich set of commands!
-
-Otherwise, why not try one of the other codelabs!
diff --git a/docs/lexicon.html b/docs/lexicon.html
index 49ee4c266..7a2ecf710 100644
--- a/docs/lexicon.html
+++ b/docs/lexicon.html
@@ -509,6 +509,15 @@
- returns a copy of this string converted to lowercase.
+
+
+ matches(pattern)
+ - returns true if the string matches the regular expression given by pattern.
+
+
diff --git a/go.mod b/go.mod
index 126ef7859..98c3c534d 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
module github.com/thought-machine/please
-go 1.23.0
+go 1.24.0
require (
cloud.google.com/go/longrunning v0.5.5
@@ -47,11 +47,11 @@ require (
github.com/zeebo/blake3 v0.2.3
go.uber.org/automaxprocs v1.5.3
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225
- golang.org/x/net v0.38.0
- golang.org/x/sync v0.12.0
- golang.org/x/sys v0.31.0
- golang.org/x/term v0.30.0
- golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d
+ golang.org/x/net v0.47.0
+ golang.org/x/sync v0.18.0
+ golang.org/x/sys v0.38.0
+ golang.org/x/term v0.37.0
+ golang.org/x/tools v0.38.0
google.golang.org/genproto/googleapis/bytestream v0.0.0-20240304212257-790db918fca8
google.golang.org/genproto/googleapis/rpc v0.0.0-20240304212257-790db918fca8
google.golang.org/grpc v1.62.1
@@ -103,9 +103,9 @@ require (
go.opentelemetry.io/otel/metric v1.24.0 // indirect
go.opentelemetry.io/otel/sdk v1.22.0 // indirect
go.opentelemetry.io/otel/trace v1.24.0 // indirect
- golang.org/x/crypto v0.36.0 // indirect
+ golang.org/x/crypto v0.45.0 // indirect
golang.org/x/oauth2 v0.27.0 // indirect
- golang.org/x/text v0.23.0 // indirect
+ golang.org/x/text v0.31.0 // indirect
golang.org/x/time v0.5.0 // indirect
google.golang.org/api v0.168.0 // indirect
google.golang.org/genproto v0.0.0-20240304212257-790db918fca8 // indirect
diff --git a/go.sum b/go.sum
index 8e50c9d4f..285e02e64 100644
--- a/go.sum
+++ b/go.sum
@@ -305,8 +305,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
-golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
+golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
+golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ=
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
@@ -334,8 +334,8 @@ golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLd
golang.org/x/net v0.0.0-20210505214959-0714010a04ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
-golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
-golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
+golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
+golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M=
golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
@@ -346,8 +346,8 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
-golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
+golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
+golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -375,21 +375,21 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
-golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
+golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
-golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
-golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
+golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
+golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
-golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
-golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
+golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
+golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -405,8 +405,8 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ=
-golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
-golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
+golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
+golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/pleasew b/pleasew
index 06c1fadf4..bf21ab79b 100755
--- a/pleasew
+++ b/pleasew
@@ -17,24 +17,44 @@ else
RESET=''
fi
+OS=""
+ARCH=""
+
+case "$(uname)" in
+ Linux)
+ OS=linux
+ case "$(uname -m)" in
+ x86_64) ARCH=amd64 ;;
+ aarch64*|armv8b|armv8l) ARCH=arm64 ;;
+ esac
+ ;;
+ Darwin)
+ OS=darwin
+ case "$(uname -m)" in
+ x86_64) ARCH=amd64 ;;
+ arm64) ARCH=arm64 ;;
+ esac
+ ;;
+ FreeBSD)
+ OS=freebsd
+ case "$(uname -m)" in
+ amd64) ARCH=amd64 ;;
+ esac
+ ;;
+ *)
+ printf >&2 '%bPlease does not support the %s operating system.%b\n' \
+ "${RED}" "$(uname)" "${RESET}"
+ exit 1
+ ;;
+esac
-DEFAULT_URL_BASE='https://get.please.build'
-
-OS="$(uname)"
-
-if [ "${OS}" = 'Darwin' ]; then
- # switch between mac amd64/arm64
- ARCH="$(uname -m)"
-else
- # default to amd64 on other operating systems
- # because we only build intel binaries
- ARCH='amd64'
+if [ -z "$ARCH" ]; then
+ printf >&2 '%bPlease does not support the %s architecture on %s.%b\n' \
+ "${RED}" "$(uname -m)" "$(uname)" "${RESET}"
+ exit 1
fi
-case "${ARCH}" in
- aarch64_be|aarch64|armv8b|armv8l) ARCH='arm64' ;;
- x86_64) ARCH='amd64' ;;
-esac
+DEFAULT_URL_BASE='https://get.please.build'
has_command () {
command -v "${1}" > /dev/null 2>&1
@@ -54,12 +74,24 @@ get_profile () {
# Check `PLZ_CONFIG_PROFILE` or fall back to arguments for a profile.
PROFILE="${PLZ_CONFIG_PROFILE:-$(get_profile "${@}")}"
+# Find repo root by traversing up until we find .plzconfig
+find_repo_root() {
+ dir="$PWD"
+ while true; do
+ [ -f "$dir/.plzconfig" ] && echo "$dir" && return 0
+ [ "$dir" = "/" ] && return 0
+ dir="$(dirname "$dir")"
+ done
+}
+
+REPO_ROOT="$(find_repo_root)"
+
# Config files on order of precedence high to low.
CONFIGS="$(cat <<- EOS
- .plzconfig.local
- ${PROFILE:+.plzconfig.${PROFILE}}
- .plzconfig_${OS}_${ARCH}
- .plzconfig
+ ${REPO_ROOT:+${REPO_ROOT}/.plzconfig.local}
+ ${REPO_ROOT:+${PROFILE:+${REPO_ROOT}/.plzconfig.${PROFILE}}}
+ ${REPO_ROOT:+${REPO_ROOT}/.plzconfig_${OS}_${ARCH}}
+ ${REPO_ROOT:+${REPO_ROOT}/.plzconfig}
${HOME}/.config/please/plzconfig
/etc/please/plzconfig
EOS
@@ -141,20 +173,7 @@ if [ "${VERSION:+x}" != 'x' ]; then
VERSION=$(${TRANSFER_TOOL} ${TRANSFER_SILENT_OPTS} "${URL_BASE}"/latest_version)
fi
-# Find the os / arch to download. You can do this quite nicely with go env
-# but we use this script on machines that don't necessarily have Go itself.
-if [ "${OS}" = 'Linux' ]; then
- GOOS='linux'
-elif [ "${OS}" = 'Darwin' ]; then
- GOOS='darwin'
-elif [ "${OS}" = 'FreeBSD' ]; then
- GOOS='freebsd'
-else
- printf >&2 '%bUnknown operating system %s%b\n' "${RED}" "${OS}" "${RESET}"
- exit 1
-fi
-
-PLEASE_URL="${URL_BASE}/${GOOS}_${ARCH}/${VERSION}/please_${VERSION}.tar.xz"
+PLEASE_URL="${URL_BASE}/${OS}_${ARCH}/${VERSION}/please_${VERSION}.tar.xz"
DIR="${LOCATION}/${VERSION}"
# Potentially we could reuse this but it's easier not to really.
diff --git a/plugins/BUILD b/plugins/BUILD
index 258ba758d..1222d0787 100644
--- a/plugins/BUILD
+++ b/plugins/BUILD
@@ -7,7 +7,7 @@ plugin_repo(
plugin_repo(
name = "cc",
plugin = "cc-rules",
- revision = "v0.5.2",
+ revision = "v0.7.1",
)
plugin_repo(
diff --git a/rules/builtins.build_defs b/rules/builtins.build_defs
index 5edca910d..1f62089b0 100644
--- a/rules/builtins.build_defs
+++ b/rules/builtins.build_defs
@@ -2,18 +2,64 @@
# Do not change the order of arguments to this function without updating the iota in targets.go to match it.
-def build_rule(name:str, cmd:str|dict='', test_cmd:str|dict='', debug_cmd:str='', srcs:list|dict=None, data:list|dict=None,
- debug_data:list|dict=None, outs:list|dict=None, deps:list=None, exported_deps:list=None, secrets:list|dict=None,
- tools:str|list|dict=None, test_tools:str|list|dict=None, debug_tools:str|list|dict=None, labels:list=None,
- visibility:list=CONFIG.DEFAULT_VISIBILITY, hashes:list=None, binary:bool=False, test:bool=False,
- test_only:bool=CONFIG.DEFAULT_TESTONLY, building_description:str=None, needs_transitive_deps:bool=False,
- output_is_complete:bool=False, sandbox:bool=CONFIG.BUILD_SANDBOX, test_sandbox:bool=CONFIG.TEST_SANDBOX,
- no_test_output:bool=False, flaky:bool|int=0, build_timeout:int|str=0, test_timeout:int|str=0, pre_build:function=None,
- post_build:function=None, requires:list=None, provides:dict=None, licences:list=CONFIG.DEFAULT_LICENCES,
- test_outputs:list=None, system_srcs:list=None, stamp:bool=False, tag:str='', optional_outs:list=None, progress:bool=False,
- size:str=None, _urls:list=None, internal_deps:list=None, pass_env:list=None, local:bool=False, output_dirs:list=[],
- exit_on_error:bool=CONFIG.EXIT_ON_ERROR, entry_points:dict={}, env:dict={}, _file_content:str=None,
- _subrepo:bool=False, no_test_coverage:bool=False, src_list_files:bool=False):
+def build_rule(
+ name:str,
+ cmd:str|dict="",
+ test_cmd:str|dict="",
+ debug_cmd:str="",
+ srcs:list|dict=None,
+ data:list|dict=None,
+ debug_data:list|dict=None,
+ outs:list|dict=None,
+ deps:list=None,
+ exported_deps:list=None,
+ runtime_deps:list=None,
+ runtime_deps_from_srcs:bool=False,
+ runtime_deps_from_deps:bool=False,
+ secrets:list|dict=None,
+ tools:str|list|dict=None,
+ test_tools:str|list|dict=None,
+ debug_tools:str|list|dict=None,
+ labels:list=None,
+ visibility:list=CONFIG.DEFAULT_VISIBILITY,
+ hashes:list=None,
+ binary:bool=False,
+ test:bool=False,
+ test_only:bool=CONFIG.DEFAULT_TESTONLY,
+ building_description:str=None,
+ needs_transitive_deps:bool=False,
+ output_is_complete:bool=False,
+ sandbox:bool=CONFIG.BUILD_SANDBOX,
+ test_sandbox:bool=CONFIG.TEST_SANDBOX,
+ no_test_output:bool=False,
+ flaky:bool|int=0,
+ build_timeout:int|str=0,
+ test_timeout:int|str=0,
+ pre_build:function=None,
+ post_build:function=None,
+ requires:list=None,
+ provides:dict=None,
+ licences:list=CONFIG.DEFAULT_LICENCES,
+ test_outputs:list=None,
+ system_srcs:list=None,
+ stamp:bool=False,
+ tag:str="",
+ optional_outs:list=None,
+ progress:bool=False,
+ size:str=None,
+ _urls:list=None,
+ internal_deps:list=None,
+ pass_env:list=None,
+ local:bool=False,
+ output_dirs:list=[],
+ exit_on_error:bool=CONFIG.EXIT_ON_ERROR,
+ entry_points:dict={},
+ env:dict={},
+ _file_content:str=None,
+ _subrepo:bool=False,
+ no_test_coverage:bool=False,
+ src_list_files:bool=False,
+):
pass
def chr(i:int) -> str:
@@ -63,6 +109,8 @@ def upper(self:str) -> str:
pass
def lower(self:str) -> str:
pass
+def matches(self:str, pattern: str) -> bool:
+ pass
def fail(msg:str):
pass
@@ -243,16 +291,18 @@ def dirname(p:str) -> str:
# Post-build callback functions.
-def get_labels(name:str, prefix:str, all:bool=False, transitive=True) -> list:
+def get_labels(name:str, prefix:str, all:bool=False, transitive:bool=None, maxdepth:int=-1) -> list:
pass
def has_label(name:str, prefix:str, all:bool=False) -> bool:
return len(get_labels(name, prefix, all)) > 0
def add_label(name:str, label:str):
pass
-def add_dep(target:str, dep:str, exported:bool=False):
+def add_dep(target:str, dep:str, exported:bool=False, runtime:bool=False):
pass
def add_exported_dep(target:str, dep:str):
- add_dep(target, dep, True)
+ add_dep(target, dep, exported=True)
+def add_runtime_dep(target:str, dep:str):
+ add_dep(target, dep, runtime=True)
def add_data(target:str, datum:str|list|dict):
pass
def add_out(target:str, name:str, out:str=''):
diff --git a/rules/misc_rules.build_defs b/rules/misc_rules.build_defs
index 3d9a260eb..0aaf7e751 100644
--- a/rules/misc_rules.build_defs
+++ b/rules/misc_rules.build_defs
@@ -1,14 +1,42 @@
"""Miscellaneous rules that aren't language-specific."""
-def genrule(name:str, cmd:str|list|dict, srcs:list|dict=None, out:str=None, outs:list|dict=None, deps:list=None,
- exported_deps:list=None, labels:list&features&tags=None, visibility:list=None,
- building_description:str='Building...', data:list|dict=None, hashes:list=None, timeout:int=0, binary:bool=False,
- sandbox:bool=None, needs_transitive_deps:bool=False, output_is_complete:bool=True,
- test_only:bool&testonly=False, secrets:list|dict=None, requires:list=None, provides:dict=None,
- pre_build:function=None, post_build:function=None, tools:str|list|dict=None, pass_env:list=None,
- local:bool=False, output_dirs:list=[], exit_on_error:bool=CONFIG.EXIT_ON_ERROR, entry_points:dict={},
- env:dict={}, optional_outs:list=[]):
+def genrule(
+ name:str,
+ cmd:str|list|dict,
+ srcs:list|dict=None,
+ out:str=None,
+ outs:list|dict=None,
+ deps:list=None,
+ exported_deps:list=None,
+ runtime_deps:list=None,
+ runtime_deps_from_srcs:bool=False,
+ runtime_deps_from_deps:bool=False,
+ labels:list&features&tags=None,
+ visibility:list=None,
+ building_description:str="Building...",
+ data:list|dict=None,
+ hashes:list=None,
+ timeout:int=0,
+ binary:bool=False,
+ sandbox:bool=None,
+ needs_transitive_deps:bool=False,
+ output_is_complete:bool=True,
+ test_only:bool&testonly=False,
+ secrets:list|dict=None,
+ requires:list=None,
+ provides:dict=None,
+ pre_build:function=None,
+ post_build:function=None,
+ tools:str|list|dict=None,
+ pass_env:list=None,
+ local:bool=False,
+ output_dirs:list=[],
+ exit_on_error:bool=CONFIG.EXIT_ON_ERROR,
+ entry_points:dict={},
+ env:dict={},
+ optional_outs:list=[],
+):
"""A general build rule which allows the user to specify a command.
Args:
@@ -34,6 +62,21 @@ def genrule(name:str, cmd:str|list|dict, srcs:list|dict=None, out:str=None, outs
names to lists, with similar semantics to those of srcs.
deps (list): Dependencies of this rule.
exported_deps (list): Dependencies that will become visible to any rules that depend on this rule.
+ runtime_deps (list): Run-time dependencies of this rule. If this rule is run (i.e. with 'plz run'),
+ rules in this list, as well as those rules' transitive run-time dependencies,
+ are guaranteed to be built before this rule runs. If this rule is declared as
+ a dependency of another rule, the outputs of rules in this list, as well as
+ the outputs of those rules' transitive run-time dependencies, will exist in
+ the dependent rule's build environment. Requires the rule to produce a runnable
+ output (i.e. binary = True).
+ runtime_deps_from_srcs (bool): If true, additionally collect run-time dependencies from this target's
+ sources. This is useful if the target's output simply collects its
+ sources in some way without eliminating their own run-time dependencies.
+ runtime_deps_from_deps (bool): If true, additionally collect run-time dependencies from this target's
+ build-time dependencies (and those targets' exported dependencies).
+ This is useful if the target's output includes its dependencies without
+ eliminating their own run-time dependencies, e.g. for targets generated
+ by the shell-rules plugin's sh_binary rule.
tools (str | list | dict): Tools used to build this rule; similar to srcs but are not copied to the
temporary build directory. Should be accessed via $(exe //path/to:tool)
or similar.
@@ -119,6 +162,9 @@ def genrule(name:str, cmd:str|list|dict, srcs:list|dict=None, out:str=None, outs
cmd = ' && '.join(cmd) if isinstance(cmd, list) else cmd,
deps = deps,
exported_deps = exported_deps,
+ runtime_deps = runtime_deps,
+ runtime_deps_from_srcs = runtime_deps_from_srcs,
+ runtime_deps_from_deps = runtime_deps_from_deps,
data = data,
tools = tools,
secrets = secrets,
@@ -146,12 +192,38 @@ def genrule(name:str, cmd:str|list|dict, srcs:list|dict=None, out:str=None, outs
)
-def gentest(name:str, test_cmd:str|list|dict, labels:list&features&tags=None, cmd:str|list|dict=None, srcs:list|dict=None,
- outs:list=None, deps:list=None, exported_deps:list=None, tools:str|list|dict=None, test_tools:str|list|dict=None,
- data:list|dict=None, visibility:list=None, timeout:int=0, needs_transitive_deps:bool=False,
- flaky:bool|int=0, secrets:list|dict=None, no_test_output:bool=False, test_outputs:list=None,
- output_is_complete:bool=True, requires:list=None, sandbox:bool=None, size:str=None, local:bool=False,
- pass_env:list=None, env:dict=None, exit_on_error:bool=CONFIG.EXIT_ON_ERROR, no_test_coverage:bool=False):
+def gentest(
+ name:str,
+ test_cmd:str|list|dict,
+ labels:list&features&tags=None,
+ cmd:str|list|dict=None,
+ srcs:list|dict=None,
+ outs:list=None,
+ deps:list=None,
+ exported_deps:list=None,
+ runtime_deps:list=None,
+ runtime_deps_from_srcs:bool=False,
+ runtime_deps_from_deps:bool=False,
+ tools:str|list|dict=None,
+ test_tools:str|list|dict=None,
+ data:list|dict=None,
+ visibility:list=None,
+ timeout:int=0,
+ needs_transitive_deps:bool=False,
+ flaky:bool|int=0,
+ secrets:list|dict=None,
+ no_test_output:bool=False,
+ test_outputs:list=None,
+ output_is_complete:bool=True,
+ requires:list=None,
+ sandbox:bool=None,
+ size:str=None,
+ local:bool=False,
+ pass_env:list=None,
+ env:dict=None,
+ exit_on_error:bool=CONFIG.EXIT_ON_ERROR,
+ no_test_coverage:bool=False,
+):
"""A rule which creates a test with an arbitrary command.
The command must return zero on success and nonzero on failure. Test results are written
@@ -172,6 +244,18 @@ def gentest(name:str, test_cmd:str|list|dict, labels:list&features&tags=None, cm
outs (list): Output files of this rule.
deps (list): Dependencies of this rule.
exported_deps (list): Dependencies that will become visible to any rules that depend on this rule.
+ runtime_deps (list): Run-time dependencies of this rule. When the test command runs, rules in this
+ list, as well as those rules' transitive run-time dependencies, will exist in
+ the test environment. Requires the rule to produce a runnable output (i.e.
+ binary = True).
+ runtime_deps_from_srcs (bool): If true, additionally collect run-time dependencies from this target's
+ sources. This is useful if the target's output simply collects its
+ sources in some way without eliminating their own run-time dependencies.
+ runtime_deps_from_deps (bool): If true, additionally collect run-time dependencies from this target's
+ build-time dependencies (and those targets' exported dependencies).
+ This is useful if the target's output includes its dependencies without
+ eliminating their own run-time dependencies, e.g. for targets generated
+ by the shell-rules plugin's sh_binary rule.
tools (str | list | dict): Tools used to build this rule; similar to srcs but are not copied to the temporary
build directory.
test_tools (str | list | dict): Like tools but available to test_cmd instead.
@@ -210,6 +294,9 @@ def gentest(name:str, test_cmd:str|list|dict, labels:list&features&tags=None, cm
outs = outs,
deps = deps,
exported_deps = exported_deps,
+ runtime_deps = runtime_deps,
+ runtime_deps_from_srcs = runtime_deps_from_srcs,
+ runtime_deps_from_deps = runtime_deps_from_deps,
data = data,
tools = tools,
test_tools = test_tools,
@@ -373,7 +460,7 @@ def system_library(name:str, srcs:list, deps:list=None, hashes:list=None,
def remote_file(name:str, url:str|list, hashes:list=None, out:str=None, binary:bool=False,
visibility:list=None, licences:list=None, test_only:bool&testonly=False,
- labels:list=[], deps:list=None, exported_deps:list=None,
+ labels:list=[], deps:list=None, exported_deps:list=None, runtime_deps:list=None,
extract:bool=False, strip_prefix:str='', _tag:str='',exported_files=[],
entry_points:dict={}, username:str=None, password_file:str=None,
headers:dict={}, secret_headers:dict={}, pass_env:list=[]):
@@ -392,6 +479,8 @@ def remote_file(name:str, url:str|list, hashes:list=None, out:str=None, binary:b
labels (list): Labels to apply to this rule.
deps (list): List of extra dependencies for this rule.
exported_deps (list): Dependencies that will become visible to any rules that depend on this rule.
+ runtime_deps (list): Run-time dependencies of this rule. Requires the rule to produce a runnable
+ output (i.e. binary = True).
extract (bool): Extracts the contents of the downloaded file. It must be either zip or
tar format.
strip_prefix (str): When extracting, strip this prefix from the extracted files.
@@ -454,6 +543,7 @@ def remote_file(name:str, url:str|list, hashes:list=None, out:str=None, binary:b
building_description = 'Extracting...',
deps = deps,
exported_deps = exported_deps,
+ runtime_deps = runtime_deps,
entry_points = entry_points,
)
@@ -484,6 +574,7 @@ def remote_file(name:str, url:str|list, hashes:list=None, out:str=None, binary:b
building_description = 'Fetching...',
deps = deps,
exported_deps = exported_deps,
+ runtime_deps = runtime_deps,
test_only = test_only,
labels = labels,
sandbox = False,
diff --git a/src/build/incrementality_test.go b/src/build/incrementality_test.go
index a2ce75cdf..02da05869 100644
--- a/src/build/incrementality_test.go
+++ b/src/build/incrementality_test.go
@@ -81,9 +81,12 @@ var KnownFields = map[string]bool{
"Debug.namedTools": true,
// These only contribute to the runtime hash, not at build time.
- "Data": true,
- "NamedData": true,
- "ContainerSettings": true,
+ "runtimeDependencies": true,
+ "RuntimeDependenciesFromSources": true,
+ "RuntimeDependenciesFromDependencies": true,
+ "Data": true,
+ "NamedData": true,
+ "ContainerSettings": true,
// These would ideally not contribute to the hash, but we need that at present
// because we don't have a good way to force a recheck of its reverse dependencies.
diff --git a/src/core/build_env.go b/src/core/build_env.go
index 732b71b9f..b3c0bcfbb 100644
--- a/src/core/build_env.go
+++ b/src/core/build_env.go
@@ -22,6 +22,7 @@ type BuildEnv map[string]string
// on any specific target etc.
func GeneralBuildEnvironment(state *BuildState) BuildEnv {
env := BuildEnv{
+ "PLZ_ENV": "1",
// Need this for certain tools, for example sass
"LANG": state.Config.Build.Lang,
// Need to know these for certain rules.
diff --git a/src/core/build_env_test.go b/src/core/build_env_test.go
index 0375e4483..ca2b22210 100644
--- a/src/core/build_env_test.go
+++ b/src/core/build_env_test.go
@@ -65,6 +65,7 @@ func TestExecEnvironment(t *testing.T) {
env := ExecEnvironment(NewDefaultBuildState(), target, "/path/to/runtime/dir")
+ assert.Equal(t, env["PLZ_ENV"], "1")
assert.Equal(t, env["DATA"], "pkg/data_file1")
assert.Equal(t, env["TMP_DIR"], "/path/to/runtime/dir")
assert.Equal(t, env["TMPDIR"], "/path/to/runtime/dir")
@@ -104,6 +105,7 @@ func TestExecEnvironmentTestTarget(t *testing.T) {
env := ExecEnvironment(state, testTarget, "/path/to/runtime/dir")
+ assert.Equal(t, env["PLZ_ENV"], "1")
assert.Equal(t, env["DATA"], "pkg/data_file1 pkg/data_file2")
assert.Equal(t, env["DATA_FILE2"], "pkg/data_file2")
assert.Equal(t, env["TOOLS"], "plz-out/bin/tool1 plz-out/bin/tool2")
@@ -138,6 +140,7 @@ func TestExecEnvironmentDebugTarget(t *testing.T) {
env := ExecEnvironment(state, target, "/path/to/runtime/dir")
+ assert.Equal(t, env["PLZ_ENV"], "1")
assert.Equal(t, env["DEBUG_DATA"], "pkg/data_file1")
assert.Equal(t, env["DEBUG_TOOLS"], "plz-out/bin/tool1")
assert.Equal(t, env["DEBUG_TOOLS_TOOL1"], "plz-out/bin/tool1")
@@ -173,6 +176,7 @@ func TestExecEnvironmentDebugTestTarget(t *testing.T) {
env := ExecEnvironment(state, testTarget, "/path/to/runtime/dir")
+ assert.Equal(t, env["PLZ_ENV"], "1")
assert.Equal(t, env["DEBUG_DATA"], "pkg/data_file1")
assert.Equal(t, env["DEBUG_TOOLS"], "plz-out/bin/tool1")
assert.Equal(t, env["DEBUG_TOOLS_TOOL1"], "plz-out/bin/tool1")
diff --git a/src/core/build_target.go b/src/core/build_target.go
index 18dc8ed2f..d15a827b5 100644
--- a/src/core/build_target.go
+++ b/src/core/build_target.go
@@ -115,6 +115,15 @@ type BuildTarget struct {
// Maps the original declaration to whatever dependencies actually got attached,
// which may be more than one in some cases. Also contains info about exporting etc.
dependencies []depInfo `name:"deps"`
+ // The run-time dependencies of this target.
+ runtimeDependencies []BuildLabel `name:"runtime_deps"`
+ // Whether to consider the run-time dependencies of this target's sources to be additional
+ // run-time dependencies of this target.
+ RuntimeDependenciesFromSources bool `name:"runtime_deps_from_srcs"`
+ // Whether to consider the run-time dependencies of this target's build-time dependencies
+ // (and exported dependencies of those targets) to be additional run-time dependencies of
+ // this target.
+ RuntimeDependenciesFromDependencies bool `name:"runtime_deps_from_deps"`
// List of build target patterns that can use this build target.
Visibility []BuildLabel
// Source files of this rule. Can refer to build rules themselves.
@@ -307,6 +316,7 @@ type depInfo struct {
resolved bool // has the graph resolved it
exported bool // is it an exported dependency
internal bool // is it an internal dependency (that is not picked up implicitly by transitive searches)
+ runtime bool // is it a run-time (and therefore implicitly transitive) dependency
source bool // is it implicit because it's a source (not true if it's a dependency too)
data bool // is it a data item for a test
}
@@ -632,7 +642,7 @@ func (target *BuildTarget) DeclaredDependenciesStrict() []BuildLabel {
defer target.mutex.RUnlock()
ret := make(BuildLabels, 0, len(target.dependencies))
for _, dep := range target.dependencies {
- if !dep.exported && !dep.source && !target.IsTool(*dep.declared) {
+ if !dep.runtime && !dep.exported && !dep.source && !target.IsTool(*dep.declared) {
ret = append(ret, *dep.declared)
}
}
@@ -672,13 +682,13 @@ func (target *BuildTarget) ExternalDependencies() []*BuildTarget {
return ret
}
-// BuildDependencies returns the build-time dependencies of this target (i.e. not data, internal nor source).
+// BuildDependencies returns the build-time dependencies of this target (i.e. not run-time dependencies, data, internal nor source).
func (target *BuildTarget) BuildDependencies() []*BuildTarget {
target.mutex.RLock()
defer target.mutex.RUnlock()
ret := make(BuildTargets, 0, len(target.dependencies))
for _, deps := range target.dependencies {
- if !deps.data && !deps.internal && !deps.source {
+ if !deps.runtime && !deps.data && !deps.internal && !deps.source {
for _, dep := range deps.deps {
ret = append(ret, dep)
}
@@ -701,6 +711,110 @@ func (target *BuildTarget) ExportedDependencies() []BuildLabel {
return ret
}
+// RuntimeDependencies returns any run-time dependencies of this target.
+//
+// Although run-time dependencies are transitive, RuntimeDependencies only returns this target's direct run-time
+// dependencies. Use IterAllRuntimeDependencies to iterate over the target's run-time dependencies transitively.
+func (target *BuildTarget) RuntimeDependencies() []BuildLabel {
+ target.mutex.RLock()
+ defer target.mutex.RUnlock()
+ ret := make(BuildLabels, 0, len(target.dependencies))
+ for _, deps := range target.dependencies {
+ if deps.runtime {
+ ret = append(ret, *deps.declared)
+ }
+ }
+ return ret
+}
+
+// IterAllRuntimeDependencies returns an iterator over the transitive run-time dependencies of this target.
+// Require/provide relationships between pairs of targets are resolved as they are with build-time dependencies.
+func (target *BuildTarget) IterAllRuntimeDependencies(graph *BuildGraph) iter.Seq[BuildLabel] {
+ var (
+ push func(*BuildTarget, func(BuildLabel) bool) bool
+ done = make(map[string]bool)
+ )
+ push = func(t *BuildTarget, yield func(BuildLabel) bool) bool {
+ if done[t.String()] {
+ return true
+ }
+ done[t.String()] = true
+ for _, dep := range t.runtimeDependencies {
+ depLabel, _ := dep.Label()
+ for _, providedDep := range graph.TargetOrDie(depLabel).ProvideFor(t) {
+ if !yield(providedDep) {
+ return false
+ }
+ if !push(graph.TargetOrDie(providedDep), yield) {
+ return false
+ }
+ }
+ }
+ // Include the run-time dependencies of data targets, but not the data targets themselves. (We needn't worry
+ // about data files here - they can't have run-time dependencies of their own.)
+ for _, data := range t.AllData() {
+ dataLabel, ok := data.Label()
+ if !ok {
+ continue
+ }
+ for _, providedDep := range graph.TargetOrDie(dataLabel).ProvideFor(t) {
+ if !push(graph.TargetOrDie(providedDep), yield) {
+ return false
+ }
+ }
+ }
+ if t.Debug != nil {
+ for _, data := range t.AllDebugData() {
+ dataLabel, ok := data.Label()
+ if !ok {
+ continue
+ }
+ for _, providedDep := range graph.TargetOrDie(dataLabel).ProvideFor(t) {
+ if !push(graph.TargetOrDie(providedDep), yield) {
+ return false
+ }
+ }
+ }
+ }
+ if t.RuntimeDependenciesFromSources || t.RuntimeDependenciesFromDependencies {
+ for _, dep := range t.dependencies {
+ // If required, include the run-time dependencies of sources, but not the sources themselves.
+ if t.RuntimeDependenciesFromSources && dep.source {
+ depLabel, _ := dep.declared.Label()
+ for _, providedDep := range graph.TargetOrDie(depLabel).ProvideFor(t) {
+ if !push(graph.TargetOrDie(providedDep), yield) {
+ return false
+ }
+ }
+ }
+ // If required, include the run-time dependencies of dependencies, but not the dependencies themselves.
+ if t.RuntimeDependenciesFromDependencies && !dep.exported && !dep.source && !dep.internal && !dep.runtime {
+ depLabel, _ := dep.declared.Label()
+ depTarget := graph.TargetOrDie(depLabel)
+ for _, providedDep := range depTarget.ProvideFor(t) {
+ if !push(graph.TargetOrDie(providedDep), yield) {
+ return false
+ }
+ }
+ // Also include the run-time dependencies of the target's exported dependencies, but not the
+ // exported dependencies themselves.
+ for _, exportedDep := range depTarget.ExportedDependencies() {
+ for _, providedDep := range graph.TargetOrDie(exportedDep).ProvideFor(t) {
+ if !push(graph.TargetOrDie(providedDep), yield) {
+ return false
+ }
+ }
+ }
+ }
+ }
+ }
+ return true
+ }
+ return func(yield func(BuildLabel) bool) {
+ push(target, yield)
+ }
+}
+
// DependenciesFor returns the dependencies that relate to a given label.
func (target *BuildTarget) DependenciesFor(label BuildLabel) []*BuildTarget {
target.mutex.RLock()
@@ -1287,7 +1401,7 @@ func (target *BuildTarget) addSource(sources []BuildInput, source BuildInput) []
}
// Add a dependency if this is not just a file.
if label, ok := source.Label(); ok {
- target.AddMaybeExportedDependency(label, false, true, false)
+ target.AddMaybeExportedDependency(label, false, true, false, false)
}
return append(sources, source)
}
@@ -1653,7 +1767,7 @@ func (target *BuildTarget) AllNamedTools() map[string][]BuildInput {
// AddDependency adds a dependency to this target. It deduplicates against any existing deps.
func (target *BuildTarget) AddDependency(dep BuildLabel) {
- target.AddMaybeExportedDependency(dep, false, false, false)
+ target.AddMaybeExportedDependency(dep, false, false, false, false)
}
// HintDependencies allocates space for at least the given number of dependencies without reallocating.
@@ -1662,17 +1776,30 @@ func (target *BuildTarget) HintDependencies(n int) {
}
// AddMaybeExportedDependency adds a dependency to this target which may be exported. It deduplicates against any existing deps.
-func (target *BuildTarget) AddMaybeExportedDependency(dep BuildLabel, exported, source, internal bool) {
+func (target *BuildTarget) AddMaybeExportedDependency(dep BuildLabel, exported, source, internal, runtime bool) {
if dep == target.Label {
log.Fatalf("Attempted to add %s as a dependency of itself.\n", dep)
}
+ if runtime {
+ if !target.IsBinary {
+ log.Fatalf("%s: output must be marked as binary to have run-time dependencies", target.String())
+ }
+ target.runtimeDependencies = append(target.runtimeDependencies, dep)
+ }
info := target.dependencyInfo(dep)
if info == nil {
- target.dependencies = append(target.dependencies, depInfo{declared: &dep, exported: exported, source: source, internal: internal})
+ target.dependencies = append(target.dependencies, depInfo{
+ declared: &dep,
+ exported: exported,
+ source: source,
+ internal: internal,
+ runtime: runtime,
+ })
} else {
info.exported = info.exported || exported
info.source = info.source && source
info.internal = info.internal && internal
+ info.runtime = info.runtime && runtime
info.data = false // It's not *only* data any more.
}
}
diff --git a/src/core/build_target_test.go b/src/core/build_target_test.go
index d9a6cea38..4360be131 100644
--- a/src/core/build_target_test.go
+++ b/src/core/build_target_test.go
@@ -4,6 +4,7 @@ package core
import (
"fmt"
"os"
+ "slices"
"testing"
"github.com/stretchr/testify/assert"
@@ -263,7 +264,7 @@ func TestAddDatum(t *testing.T) {
assert.Equal(t, target1.Data, []BuildInput{target2.Label})
assert.True(t, target1.dependencies[0].data)
// Now we add it as a dependency too, which unsets the data label
- target1.AddMaybeExportedDependency(target2.Label, false, false, false)
+ target1.AddMaybeExportedDependency(target2.Label, false, false, false, false)
assert.False(t, target1.dependencies[0].data)
}
@@ -427,20 +428,64 @@ func TestBuildDependencies(t *testing.T) {
target1 := makeTarget1("//src/core:target1", "")
target2 := makeTarget1("//src/core:target2", "", target1)
target3 := makeTarget1("//src/core:target3", "", target2)
+ target4 := makeTarget1("//src/core:target4", "")
+ target5 := makeTarget1("//src/core:target5", "")
target3.AddDatum(target1.Label)
+ // BuildDependencies shouldn't return run-time dependencies:
+ target5.IsBinary = true
+ target5.AddMaybeExportedDependency(target4.Label, false, false, false, true) // runtime
assert.Equal(t, []*BuildTarget{}, target1.BuildDependencies())
assert.Equal(t, []*BuildTarget{target1}, target2.BuildDependencies())
assert.Equal(t, []*BuildTarget{target2}, target3.BuildDependencies())
+ assert.Equal(t, []*BuildTarget{}, target5.BuildDependencies())
}
func TestDeclaredDependenciesStrict(t *testing.T) {
target1 := makeTarget1("//src/core:target1", "")
target2 := makeTarget1("//src/core:target2", "", target1)
target3 := makeTarget1("//src/core:target3", "", target2)
- target3.AddMaybeExportedDependency(target1.Label, true, false, false)
+ target4 := makeTarget1("//src/core:target4", "")
+ target5 := makeTarget1("//src/core:target5", "")
+ target3.AddMaybeExportedDependency(target1.Label, true, false, false, false)
+ // DeclaredDependenciesStrict shouldn't return run-time dependencies:
+ target5.IsBinary = true
+ target5.AddMaybeExportedDependency(target4.Label, false, false, false, true) // runtime
assert.Equal(t, []BuildLabel{}, target1.DeclaredDependenciesStrict())
assert.Equal(t, []BuildLabel{target1.Label}, target2.DeclaredDependenciesStrict())
assert.Equal(t, []BuildLabel{target2.Label}, target3.DeclaredDependenciesStrict())
+ assert.Equal(t, []*BuildTarget{}, target5.BuildDependencies())
+}
+
+func TestRuntimeDependencies(t *testing.T) {
+ target1 := makeTarget1("//src/core:target1", "")
+ target2 := makeTarget1("//src/core:target2", "")
+ target3 := makeTarget1("//src/core:target3", "")
+ target2.IsBinary = true
+ target2.AddMaybeExportedDependency(target1.Label, false, false, false, true) // runtime
+ target3.IsBinary = true
+ target3.AddMaybeExportedDependency(target2.Label, false, false, false, true) // runtime
+ // RuntimeDependencies shouldn't return transitive run-time dependencies.
+ assert.Equal(t, []BuildLabel{}, target1.RuntimeDependencies())
+ assert.Equal(t, []BuildLabel{target1.Label}, target2.RuntimeDependencies())
+ assert.Equal(t, []BuildLabel{target2.Label}, target3.RuntimeDependencies())
+}
+
+func TestIterAllRuntimeDependencies(t *testing.T) {
+ target1 := makeTarget1("//src/core:target1", "")
+ target2 := makeTarget1("//src/core:target2", "")
+ target3 := makeTarget1("//src/core:target3", "")
+ target2.IsBinary = true
+ target2.AddMaybeExportedDependency(target1.Label, false, false, false, true) // runtime
+ target3.IsBinary = true
+ target3.AddMaybeExportedDependency(target2.Label, false, false, false, true) // runtime
+ graph := NewGraph()
+ graph.AddTarget(target1)
+ graph.AddTarget(target2)
+ graph.AddTarget(target3)
+ // IterAllRuntimeDependencies should yield transitive run-time dependencies.
+ assert.Nil(t, slices.Collect(target1.IterAllRuntimeDependencies(graph)))
+ assert.ElementsMatch(t, []BuildLabel{target1.Label}, slices.Collect(target2.IterAllRuntimeDependencies(graph)))
+ assert.ElementsMatch(t, []BuildLabel{target1.Label, target2.Label}, slices.Collect(target3.IterAllRuntimeDependencies(graph)))
}
func TestAddDependency(t *testing.T) {
@@ -451,7 +496,7 @@ func TestAddDependency(t *testing.T) {
target2.AddDependency(target1.Label)
assert.Equal(t, []BuildLabel{target1.Label}, target2.DeclaredDependencies())
assert.Equal(t, []BuildLabel{}, target2.ExportedDependencies())
- target2.AddMaybeExportedDependency(target1.Label, true, false, false)
+ target2.AddMaybeExportedDependency(target1.Label, true, false, false, false)
assert.Equal(t, []BuildLabel{target1.Label}, target2.DeclaredDependencies())
assert.Equal(t, []BuildLabel{target1.Label}, target2.ExportedDependencies())
assert.Equal(t, []*BuildTarget{}, target2.Dependencies())
@@ -459,13 +504,25 @@ func TestAddDependency(t *testing.T) {
assert.Equal(t, []*BuildTarget{target1}, target2.Dependencies())
}
+func TestAddRuntimeDependency(t *testing.T) {
+ target1 := makeTarget1("//src/core:target1", "PUBLIC")
+ target2 := makeTarget1("//src/core:target2", "PUBLIC")
+ target1.IsBinary = true
+ target1.AddMaybeExportedDependency(target2.Label, false, false, false, true) // runtime
+ assert.Equal(t, target1.runtimeDependencies, []BuildLabel{target2.Label})
+ assert.True(t, target1.dependencies[0].runtime)
+ // Now we add it as a build-time dependency too, which should unset the runtime flag.
+ target1.AddMaybeExportedDependency(target2.Label, false, false, false, false)
+ assert.False(t, target1.dependencies[0].runtime)
+}
+
func TestAddDependencySource(t *testing.T) {
target1 := makeTarget1("//src/core:target1", "")
target2 := makeTarget1("//src/core:target2", "")
- target2.AddMaybeExportedDependency(target1.Label, true, true, false)
+ target2.AddMaybeExportedDependency(target1.Label, true, true, false, false)
assert.True(t, target2.IsSourceOnlyDep(target1.Label))
// N.B. It's important that calling this again cancels the source flag.
- target2.AddMaybeExportedDependency(target1.Label, true, false, false)
+ target2.AddMaybeExportedDependency(target1.Label, true, false, false, false)
assert.False(t, target2.IsSourceOnlyDep(target1.Label))
}
diff --git a/src/core/state.go b/src/core/state.go
index 3596885b5..18a03d70a 100644
--- a/src/core/state.go
+++ b/src/core/state.go
@@ -1353,18 +1353,44 @@ func (state *BuildState) IterInputs(target *BuildTarget, test bool) iter.Seq[Bui
return IterInputs(state, state.Graph, target, true, target.IsFilegroup)
}
return func(yield func(BuildInput) bool) {
+ // The target itself, plus its transitive run-time dependencies (since we're about to run the target):
if !yield(target.Label) {
return
}
+ for runDep := range target.IterAllRuntimeDependencies(state.Graph) {
+ if !yield(runDep) {
+ return
+ }
+ }
+ // The target's data, plus the transitive run-time dependencies for data that are also targets:
for _, datum := range target.AllData() {
if !yield(datum) {
return
}
+ label, ok := datum.Label()
+ if !ok {
+ continue
+ }
+ for runDep := range state.Graph.TargetOrDie(label).IterAllRuntimeDependencies(state.Graph) {
+ if !yield(runDep) {
+ return
+ }
+ }
}
+ // The target's test tools, plus the transitive run-time dependencies for test tools that are also targets:
for _, tool := range target.AllTestTools() {
if !yield(tool) {
return
}
+ label, ok := tool.Label()
+ if !ok {
+ continue
+ }
+ for runDep := range state.Graph.TargetOrDie(label).IterAllRuntimeDependencies(state.Graph) {
+ if !yield(runDep) {
+ return
+ }
+ }
}
}
}
diff --git a/src/core/utils.go b/src/core/utils.go
index abc7998cf..3c63f5351 100644
--- a/src/core/utils.go
+++ b/src/core/utils.go
@@ -146,6 +146,11 @@ func IterInputs(state *BuildState, graph *BuildGraph, target *BuildTarget, inclu
if !yield(p) {
return false
}
+ for runDep := range graph.TargetOrDie(p).IterAllRuntimeDependencies(graph) {
+ if !yield(runDep) {
+ return false
+ }
+ }
}
return true
}
@@ -157,6 +162,11 @@ func IterInputs(state *BuildState, graph *BuildGraph, target *BuildTarget, inclu
if !yield(dependency.Label) {
return false
}
+ for runDep := range graph.TargetOrDie(dependency.Label).IterAllRuntimeDependencies(graph) {
+ if !yield(runDep) {
+ return false
+ }
+ }
}
done[dependency.Label] = true
@@ -257,6 +267,15 @@ func IterRuntimeFiles(graph *BuildGraph, target *BuildTarget, absoluteOuts bool,
}
}
+ for runDep := range target.IterAllRuntimeDependencies(graph) {
+ fullPaths := runDep.FullPaths(graph)
+ for i, depPath := range runDep.Paths(graph) {
+ if !pushOut(fullPaths[i], depPath) {
+ return
+ }
+ }
+ }
+
for _, data := range target.AllData() {
fullPaths := data.FullPaths(graph)
for i, dataPath := range data.Paths(graph) {
@@ -264,6 +283,18 @@ func IterRuntimeFiles(graph *BuildGraph, target *BuildTarget, absoluteOuts bool,
return
}
}
+ label, ok := data.Label()
+ if !ok {
+ continue
+ }
+ for runDep := range graph.TargetOrDie(label).IterAllRuntimeDependencies(graph) {
+ fullPaths := runDep.FullPaths(graph)
+ for i, depPath := range runDep.Paths(graph) {
+ if !pushOut(fullPaths[i], depPath) {
+ return
+ }
+ }
+ }
}
if target.Test != nil {
@@ -274,6 +305,18 @@ func IterRuntimeFiles(graph *BuildGraph, target *BuildTarget, absoluteOuts bool,
return
}
}
+ label, ok := tool.Label()
+ if !ok {
+ continue
+ }
+ for runDep := range graph.TargetOrDie(label).IterAllRuntimeDependencies(graph) {
+ fullPaths := runDep.FullPaths(graph)
+ for i, depPath := range runDep.Paths(graph) {
+ if !pushOut(fullPaths[i], depPath) {
+ return
+ }
+ }
+ }
}
}
@@ -285,6 +328,18 @@ func IterRuntimeFiles(graph *BuildGraph, target *BuildTarget, absoluteOuts bool,
return
}
}
+ label, ok := data.Label()
+ if !ok {
+ continue
+ }
+ for runDep := range graph.TargetOrDie(label).IterAllRuntimeDependencies(graph) {
+ fullPaths := runDep.FullPaths(graph)
+ for i, depPath := range runDep.Paths(graph) {
+ if !pushOut(fullPaths[i], depPath) {
+ return
+ }
+ }
+ }
}
for _, tool := range target.AllDebugTools() {
fullPaths := tool.FullPaths(graph)
@@ -293,6 +348,18 @@ func IterRuntimeFiles(graph *BuildGraph, target *BuildTarget, absoluteOuts bool,
return
}
}
+ label, ok := tool.Label()
+ if !ok {
+ continue
+ }
+ for runDep := range graph.TargetOrDie(label).IterAllRuntimeDependencies(graph) {
+ fullPaths := runDep.FullPaths(graph)
+ for i, depPath := range runDep.Paths(graph) {
+ if !pushOut(fullPaths[i], depPath) {
+ return
+ }
+ }
+ }
}
}
}
diff --git a/src/export/export.go b/src/export/export.go
index 905be937a..2863dfef0 100644
--- a/src/export/export.go
+++ b/src/export/export.go
@@ -37,6 +37,10 @@ func ToDir(state *core.BuildState, dir string, noTrim bool, targets []core.Build
exportedTargets: map[core.BuildLabel]bool{},
}
+ if err := os.MkdirAll(dir, fs.DirPermissions); err != nil {
+ log.Fatalf("failed to create export directory %s: %v", dir, err)
+ }
+
e.exportPlzConf()
for _, target := range state.Config.Parse.PreloadSubincludes {
for _, includeLabel := range append(state.Graph.TransitiveSubincludes(target), target) {
diff --git a/src/parse/asp/builtins.go b/src/parse/asp/builtins.go
index c34479f30..353478b07 100644
--- a/src/parse/asp/builtins.go
+++ b/src/parse/asp/builtins.go
@@ -7,6 +7,7 @@ import (
"io"
"path/filepath"
"reflect"
+ "regexp"
"slices"
"sort"
"strconv"
@@ -47,8 +48,8 @@ func registerBuiltins(s *scope) {
setNativeCode(s, "zip", zip, varargs)
setNativeCode(s, "any", anyFunc)
setNativeCode(s, "all", allFunc)
- setNativeCode(s, "min", min)
- setNativeCode(s, "max", max)
+ setNativeCode(s, "min", minFunc)
+ setNativeCode(s, "max", maxFunc)
setNativeCode(s, "chr", chr)
setNativeCode(s, "ord", ord)
setNativeCode(s, "len", lenFunc)
@@ -98,6 +99,7 @@ func registerBuiltins(s *scope) {
"count": setNativeCode(s, "count", strCount),
"upper": setNativeCode(s, "upper", strUpper),
"lower": setNativeCode(s, "lower", strLower),
+ "matches": setNativeCode(s, "matches", strMatches),
}
s.interpreter.stringMethods["format"].kwargs = true
s.interpreter.dictMethods = map[string]*pyFunc{
@@ -645,6 +647,20 @@ func strLower(s *scope, args []pyObject) pyObject {
return pyString(strings.ToLower(self))
}
+func strMatches(s *scope, args []pyObject) pyObject {
+ self := string(args[0].(pyString))
+ pattern := string(args[1].(pyString))
+ compiledRegex := s.interpreter.regexCache.Get(pattern)
+ if compiledRegex == nil {
+ compiled, err := regexp.Compile(pattern)
+ s.Assert(err == nil, "%s", err)
+ // We don't need to check if another task inserted the regex first, as it will be an identical result.
+ s.interpreter.regexCache.Add(pattern, compiled)
+ compiledRegex = compiled
+ }
+ return newPyBool(compiledRegex.MatchString(self))
+}
+
func boolType(s *scope, args []pyObject) pyObject {
return newPyBool(args[0].IsTruthy())
}
@@ -1030,11 +1046,11 @@ func allFunc(s *scope, args []pyObject) pyObject {
return True
}
-func min(s *scope, args []pyObject) pyObject {
+func minFunc(s *scope, args []pyObject) pyObject {
return extreme(s, args, LessThan)
}
-func max(s *scope, args []pyObject) pyObject {
+func maxFunc(s *scope, args []pyObject) pyObject {
return extreme(s, args, GreaterThan)
}
@@ -1093,13 +1109,20 @@ func getLabels(s *scope, args []pyObject) pyObject {
name := string(args[0].(pyString))
prefix := string(args[1].(pyString))
all := args[2].IsTruthy()
- transitive := args[3].IsTruthy()
+ transitive := args[3]
+ maxDepth := int(args[4].(pyInt))
+ if transitiveBool, ok := transitive.(pyBool); ok {
+ s.Assert(maxDepth == -1, "get_labels: only one of transitive and maxdepth may be specified, not both")
+ if !transitiveBool.IsTruthy() {
+ maxDepth = 0
+ }
+ }
if core.LooksLikeABuildLabel(name) {
label := core.ParseBuildLabel(name, s.pkg.Name)
- return getLabelsInternal(s.state.Graph.TargetOrDie(label), prefix, core.Built, all, transitive)
+ return getLabelsInternal(s.state.Graph.TargetOrDie(label), prefix, core.Built, all, maxDepth)
}
target := getTargetPost(s, name)
- return getLabelsInternal(target, prefix, core.Building, all, transitive)
+ return getLabelsInternal(target, prefix, core.Building, all, maxDepth)
}
// addLabel adds a set of labels to the named rule
@@ -1119,35 +1142,35 @@ func addLabel(s *scope, args []pyObject) pyObject {
return None
}
-func getLabelsInternal(target *core.BuildTarget, prefix string, minState core.BuildTargetState, all, transitive bool) pyObject {
+func getLabelsInternal(target *core.BuildTarget, prefix string, minState core.BuildTargetState, all bool, maxDepth int) pyObject {
if target.State() < minState {
log.Fatalf("get_labels called on a target that is not yet built: %s", target.Label)
}
- if all && !transitive {
- log.Fatalf("get_labels can't be called with all set to true when transitive is set to False")
+ if all && maxDepth != -1 {
+ log.Fatalf("get_labels: if all is True, transitive must be True or maxdepth must be -1")
}
labels := map[string]bool{}
done := map[*core.BuildTarget]bool{}
- var getLabels func(*core.BuildTarget)
- getLabels = func(t *core.BuildTarget) {
+ var getLabels func(*core.BuildTarget, int)
+ getLabels = func(t *core.BuildTarget, depth int) {
for _, label := range t.Labels {
if strings.HasPrefix(label, prefix) {
labels[strings.TrimSpace(strings.TrimPrefix(label, prefix))] = true
}
}
- if !transitive {
+ done[t] = true
+ if depth == 0 {
return
}
- done[t] = true
if !t.OutputIsComplete || t == target || all {
for _, dep := range t.Dependencies() {
if !done[dep] {
- getLabels(dep)
+ getLabels(dep, max(depth-1, -1))
}
}
}
}
- getLabels(target)
+ getLabels(target, maxDepth)
ret := make([]string, len(labels))
i := 0
for label := range labels {
@@ -1176,7 +1199,8 @@ func addDep(s *scope, args []pyObject) pyObject {
target := getTargetPost(s, string(args[0].(pyString)))
dep := s.parseLabelInPackage(string(args[1].(pyString)), s.pkg)
exported := args[2].IsTruthy()
- target.AddMaybeExportedDependency(dep, exported, false, false)
+ runtime := args[3].IsTruthy()
+ target.AddMaybeExportedDependency(dep, exported, false, false, runtime)
// Queue this dependency if it'll be needed.
if target.State() > core.Inactive {
err := s.state.QueueTarget(dep, target.Label, false, core.ParseModeNormal)
diff --git a/src/parse/asp/builtins_test.go b/src/parse/asp/builtins_test.go
index dbe3764f6..02e622b0c 100644
--- a/src/parse/asp/builtins_test.go
+++ b/src/parse/asp/builtins_test.go
@@ -21,28 +21,43 @@ func TestPackageName(t *testing.T) {
func TestGetLabels(t *testing.T) {
state := core.NewBuildState(core.DefaultConfiguration())
- foo := core.NewBuildTarget(core.NewBuildLabel("pkg", "foo"))
- foo.AddLabel("cc:ld:-ldl")
- foo.SetState(core.Built)
- bar := core.NewBuildTarget(core.NewBuildLabel("pkg", "bar"))
- bar.AddDependency(foo.Label)
- bar.AddLabel("cc:ld:-pthread")
- bar.SetState(core.Built)
+ bottom := core.NewBuildTarget(core.NewBuildLabel("pkg", "bottom"))
+ bottom.AddLabel("target:bottom")
+ bottom.SetState(core.Built)
- state.Graph.AddTarget(foo)
- state.Graph.AddTarget(bar)
+ middle := core.NewBuildTarget(core.NewBuildLabel("pkg", "middle"))
+ middle.AddDependency(bottom.Label)
+ middle.AddLabel("target:middle")
+ middle.SetState(core.Built)
- err := bar.ResolveDependencies(state.Graph)
+ top := core.NewBuildTarget(core.NewBuildLabel("pkg", "top"))
+ top.AddDependency(middle.Label)
+ top.AddLabel("target:top")
+ top.SetState(core.Built)
+
+ state.Graph.AddTarget(bottom)
+ state.Graph.AddTarget(middle)
+ state.Graph.AddTarget(top)
+
+ err := middle.ResolveDependencies(state.Graph)
+ require.NoError(t, err)
+ err = top.ResolveDependencies(state.Graph)
require.NoError(t, err)
s := &scope{state: state, pkg: core.NewPackage("pkg")}
- ls := getLabels(s, []pyObject{pyString(":bar"), pyString("cc:ld:"), False, True}).(pyList)
- assert.Len(t, ls, 2)
-
- ls = getLabels(s, []pyObject{pyString(":bar"), pyString("cc:ld:"), False, False}).(pyList)
- assert.Len(t, ls, 1)
- assert.Equal(t, pyString("-pthread"), ls[0])
+ ls := getLabels(s, []pyObject{pyString(":top"), pyString("target:"), False, True, pyInt(-1)}).(pyList) // transitive=True
+ assert.Equal(t, pyList{pyString("bottom"), pyString("middle"), pyString("top")}, ls)
+ ls = getLabels(s, []pyObject{pyString(":top"), pyString("target:"), False, None, pyInt(-1)}).(pyList) // maxdepth=-1 (equivalent to above)
+ assert.Equal(t, pyList{pyString("bottom"), pyString("middle"), pyString("top")}, ls)
+
+ ls = getLabels(s, []pyObject{pyString(":top"), pyString("target:"), False, False, pyInt(-1)}).(pyList) // transitive=False
+ assert.Equal(t, pyList{pyString("top")}, ls)
+ ls = getLabels(s, []pyObject{pyString(":top"), pyString("target:"), False, None, pyInt(0)}).(pyList) // maxdepth=0 (equivalent to above)
+ assert.Equal(t, pyList{pyString("top")}, ls)
+
+ ls = getLabels(s, []pyObject{pyString(":top"), pyString("target:"), False, None, pyInt(1)}).(pyList) // maxdepth=1
+ assert.Equal(t, pyList{pyString("middle"), pyString("top")}, ls)
}
func TestTag(t *testing.T) {
diff --git a/src/parse/asp/interpreter.go b/src/parse/asp/interpreter.go
index 4d8199439..93e4ca0a3 100644
--- a/src/parse/asp/interpreter.go
+++ b/src/parse/asp/interpreter.go
@@ -6,6 +6,7 @@ import (
"iter"
"path/filepath"
"reflect"
+ "regexp"
"runtime/debug"
"runtime/pprof"
"strings"
@@ -31,6 +32,8 @@ type interpreter struct {
limiter semaphore
stringMethods, dictMethods, configMethods map[string]*pyFunc
+
+ regexCache *cmap.Map[string, *regexp.Regexp]
}
// newInterpreter creates and returns a new interpreter instance.
@@ -42,10 +45,11 @@ func newInterpreter(state *core.BuildState, p *Parser) *interpreter {
locals: map[string]pyObject{},
}
i := &interpreter{
- scope: s,
- parser: p,
- configs: map[*core.BuildState]*pyConfig{},
- limiter: make(semaphore, state.Config.Parse.NumThreads),
+ scope: s,
+ parser: p,
+ configs: map[*core.BuildState]*pyConfig{},
+ limiter: make(semaphore, state.Config.Parse.NumThreads),
+ regexCache: cmap.New[string, *regexp.Regexp](cmap.SmallShardCount, cmap.XXHash),
}
// If we're creating an interpreter for a subrepo, we should share the subinclude cache.
if p.interpreter != nil {
diff --git a/src/parse/asp/targets.go b/src/parse/asp/targets.go
index 94c656830..48404b792 100644
--- a/src/parse/asp/targets.go
+++ b/src/parse/asp/targets.go
@@ -30,6 +30,9 @@ const (
outsBuildRuleArgIdx
depsBuildRuleArgIdx
exportedDepsBuildRuleArgIdx
+ runtimeDepsBuildRuleArgIdx
+ runtimeDepsFromSrcsBuildRuleArgIdx
+ runtimeDepsFromDepsBuildRuleArgIdx
secretsBuildRuleArgIdx
toolsBuildRuleArgIdx
testToolsBuildRuleArgIdx
@@ -141,6 +144,11 @@ func createTarget(s *scope, args []pyObject) *core.BuildTarget {
if target.IsRemoteFile {
target.AddLabel("remote")
}
+ // filegroups don't really produce any outputs of their own - they're just the filegroup's own sources.
+ // The run-time dependencies of a filegroup's sources should therefore be treated as the filegroup's own
+ // run-time dependencies.
+ target.RuntimeDependenciesFromSources = target.IsFilegroup || isTruthy(runtimeDepsFromSrcsBuildRuleArgIdx)
+ target.RuntimeDependenciesFromDependencies = isTruthy(runtimeDepsFromDepsBuildRuleArgIdx)
target.Command, target.Commands = decodeCommands(s, args[cmdBuildRuleArgIdx])
if test {
target.Test = new(core.TestFields)
@@ -270,9 +278,10 @@ func populateTarget(s *scope, t *core.BuildTarget, args []pyObject) {
addMaybeNamedOutput(s, "outs", args[outsBuildRuleArgIdx], t.AddOutput, t.AddNamedOutput, t, false)
addMaybeNamedOutput(s, "optional_outs", args[optionalOutsBuildRuleArgIdx], t.AddOptionalOutput, nil, t, true)
t.HintDependencies(depLen(args[depsBuildRuleArgIdx]) + depLen(args[exportedDepsBuildRuleArgIdx]) + depLen(args[internalDepsBuildRuleArgIdx]))
- addDependencies(s, "deps", args[depsBuildRuleArgIdx], t, false, false)
- addDependencies(s, "exported_deps", args[exportedDepsBuildRuleArgIdx], t, true, false)
- addDependencies(s, "internal_deps", args[internalDepsBuildRuleArgIdx], t, false, true)
+ addDependencies(s, "deps", args[depsBuildRuleArgIdx], t, false, false, false)
+ addDependencies(s, "exported_deps", args[exportedDepsBuildRuleArgIdx], t, true, false, false)
+ addDependencies(s, "internal_deps", args[internalDepsBuildRuleArgIdx], t, false, true, false)
+ addDependencies(s, "runtime_deps", args[runtimeDepsBuildRuleArgIdx], t, false, false, true)
addStrings(s, "labels", args[labelsBuildRuleArgIdx], t.AddLabel)
addStrings(s, "hashes", args[hashesBuildRuleArgIdx], t.AddHash)
addStrings(s, "licences", args[licencesBuildRuleArgIdx], t.AddLicence)
@@ -472,13 +481,13 @@ func addMaybeNamedSecret(s *scope, name string, obj pyObject, anon func(string),
}
// addDependencies adds dependencies to a target, which may or may not be exported.
-func addDependencies(s *scope, name string, obj pyObject, target *core.BuildTarget, exported, internal bool) {
+func addDependencies(s *scope, name string, obj pyObject, target *core.BuildTarget, exported, internal, runtime bool) {
addStrings(s, name, obj, func(str string) {
if s.state.Config.Bazel.Compatibility && !core.LooksLikeABuildLabel(str) && !strings.HasPrefix(str, "@") {
// *sigh*... Bazel seems to allow an implicit : on the start of dependencies
str = ":" + str
}
- target.AddMaybeExportedDependency(assertNotPseudoLabel(s, s.parseLabelInPackage(str, s.pkg)), exported, false, internal)
+ target.AddMaybeExportedDependency(assertNotPseudoLabel(s, s.parseLabelInPackage(str, s.pkg)), exported, false, internal, runtime)
})
}
diff --git a/src/parse/internal.tmpl b/src/parse/internal.tmpl
index be7a29888..90cfe6b67 100644
--- a/src/parse/internal.tmpl
+++ b/src/parse/internal.tmpl
@@ -1,14 +1,10 @@
remote_file(
name = "arcat",
- url = f"https://github.com/please-build/arcat/releases/download/v1.2.1/arcat-1.2.1-{CONFIG.HOSTOS}_{CONFIG.HOSTARCH}",
+ url = f"https://github.com/please-build/arcat/releases/download/v1.3.1/arcat-1.3.1-{CONFIG.HOSTOS}_{CONFIG.HOSTARCH}",
out = "arcat",
binary = True,
hashes = [
- "507958f2e44e5de7529cb85fa9137ddcca2293daf6ef30dbc1a1cfa22e86ee96", # darwin_amd64
- "717cb15f1237010740be50df3561027a976ac5bd2a5d8f5c30d2ea57ccbcad82", # darwin_arm64
- "190fbf9cdcbcf53a82b886076c45a07323eeb6b4f460485c645cf2f306927c0c", # freebsd_amd64
- "e5b23a127c093939f21bf2f8cb66635b7f0b88cf8a3d1fca1bc19a114f5c5a0c", # linux_amd64
- "019ee52b534a3c48028e9a7229990055432af24a23892619e1a0d28eb3265245", # linux_arm64
+ "{{ .ArcatHash }}", # defined in internal_package.go
],
visibility = ["PUBLIC"],
)
@@ -29,3 +25,4 @@ genrule(
binary = True,
)
{{ end }}
+
diff --git a/src/parse/internal_package.go b/src/parse/internal_package.go
index c2d77bd13..b867eca57 100644
--- a/src/parse/internal_package.go
+++ b/src/parse/internal_package.go
@@ -27,15 +27,33 @@ func GetInternalPackage(config *core.Configuration) (string, error) {
url = fmt.Sprintf("%s/%s_%s/%s/please_tools_%s.tar.xz", config.Please.DownloadLocation, runtime.GOOS, runtime.GOARCH, version.PleaseVersion, version.PleaseVersion)
}
+ var arcatHash string
+ switch fmt.Sprintf("%s_%s", runtime.GOOS, runtime.GOARCH) {
+ case "darwin_amd64":
+ arcatHash = "6af2cf108592535701aa9395f3a5deeb48a5dfbe8174a8ebe3d56bb93de2c255"
+ case "darwin_arm64":
+ arcatHash = "5070ef05d14c66a85d438f400c6ff734a23833929775d6824b69207b704034bf"
+ case "freebsd_amd64":
+ arcatHash = "05ad6ac45be3a4ca1238bb1bd09207a596f8ff5f885415f8df4ff2dc849fa04e"
+ case "linux_amd64":
+ arcatHash = "aec85425355291e515cd10ac0addec3a5bc9e05c9d07af01aca8c34aaf0f1222"
+ case "linux_arm64":
+ arcatHash = "8266cb95cc84b23642bca6567f8b4bd18de399c887cb5845ab6a901d0dba54d2"
+ default:
+ return "", fmt.Errorf("arcat tool not supported for platform: %s_%s", runtime.GOOS, runtime.GOARCH)
+ }
+
data := struct {
- ToolsURL string
- Tools []string
+ ToolsURL string
+ Tools []string
+ ArcatHash string
}{
ToolsURL: url,
Tools: []string{
"build_langserver",
"please_sandbox",
},
+ ArcatHash: arcatHash,
}
var buf bytes.Buffer
diff --git a/src/query/print.go b/src/query/print.go
index 9d5524bd3..0c551055d 100644
--- a/src/query/print.go
+++ b/src/query/print.go
@@ -137,6 +137,9 @@ func specialFields() specialFieldsMap {
"exported_deps": func(target *core.BuildTarget) interface{} {
return target.ExportedDependencies()
},
+ "runtime_deps": func(target *core.BuildTarget) interface{} {
+ return target.RuntimeDependencies()
+ },
"visibility": func(target *core.BuildTarget) interface{} {
if len(target.Visibility) == 1 && target.Visibility[0] == core.WholeGraph[0] {
return []string{"PUBLIC"}
diff --git a/src/remote/remote.go b/src/remote/remote.go
index e7fe68fe2..ee7ec220d 100644
--- a/src/remote/remote.go
+++ b/src/remote/remote.go
@@ -351,10 +351,13 @@ func (c *Client) Build(target *core.BuildTarget) (*core.BuildMetadata, error) {
if err := c.Download(target); err != nil {
return metadata, err
}
- // TODO(peterebden): Should this not just be part of Download()?
+ // TODO(peterebden): Should these not just be part of Download()?
if err := c.downloadData(target); err != nil {
return metadata, err
}
+ if err := c.downloadRuntimeDependencies(target); err != nil {
+ return metadata, err
+ }
}
return metadata, nil
}
@@ -376,6 +379,22 @@ func (c *Client) downloadData(target *core.BuildTarget) error {
return g.Wait()
}
+// downloadRuntimeDependencies downloads all the runtime dependencies for a target.
+func (c *Client) downloadRuntimeDependencies(target *core.BuildTarget) error {
+ var g errgroup.Group
+ for runDep := range target.IterAllRuntimeDependencies(c.state.Graph) {
+ l, _ := runDep.Label()
+ t := c.state.Graph.TargetOrDie(l)
+ g.Go(func() error {
+ if err := c.Download(t); err != nil {
+ return err
+ }
+ return nil
+ })
+ }
+ return g.Wait()
+}
+
// Run runs a target on the remote executors.
func (c *Client) Run(target *core.BuildTarget) error {
if err := c.CheckInitialised(); err != nil {
diff --git a/test/builtins/BUILD b/test/builtins/BUILD
new file mode 100644
index 000000000..ccb21ec42
--- /dev/null
+++ b/test/builtins/BUILD
@@ -0,0 +1,7 @@
+subinclude("//test/build_defs")
+
+please_repo_e2e_test(
+ name = "strings_test",
+ plz_command = "plz build",
+ repo = "strings",
+)
diff --git a/test/builtins/strings/.plzconfig b/test/builtins/strings/.plzconfig
new file mode 100644
index 000000000..ea85e4f73
--- /dev/null
+++ b/test/builtins/strings/.plzconfig
@@ -0,0 +1,2 @@
+[parse]
+BuildFileName = BUILD_FILE
diff --git a/test/builtins/strings/BUILD_FILE b/test/builtins/strings/BUILD_FILE
new file mode 100644
index 000000000..8d7275107
--- /dev/null
+++ b/test/builtins/strings/BUILD_FILE
@@ -0,0 +1,45 @@
+
+assert ",".join(["a","b","c"]) == "a,b,c"
+
+assert "a,b,c".split(",") == ["a", "b", "c"]
+
+assert "abc".replace("bc", "ab") == "aab"
+
+pre, sep, post = "a,b,c".partition(",")
+assert pre == "a" and sep == "," and post == "b,c"
+
+pre, sep, post = "a,b,c".rpartition(",")
+assert pre == "a,b" and sep == "," and post == "c"
+
+assert "abc".startswith("ab") == True
+
+assert "abc".endswith("bc") == True
+
+assert "a{var1},b{var2},c{var3}".format(var1="a", var2=2, var3=[3]) == "aa,b2,c[3]"
+
+assert "abcba".lstrip("a") == "bcba"
+
+assert "abcba".rstrip("a") == "abcb"
+
+assert "abcba".strip("a") == "bcb"
+
+assert "abc".removeprefix("ab") == "c"
+
+assert "abc".removesuffix("bc") == "a"
+
+assert "abcba".find("b") == 1
+
+assert "abcba".rfind("b") == 3
+
+assert "abcba".count("b") == 2
+
+assert "abc".upper() == "ABC"
+
+assert "ABC".lower() == "abc"
+
+
+assert "abc".matches("a.c")
+
+assert "abbbbbbc".matches("a.*c")
+
+assert not "abc".matches("$b")
diff --git a/test/get_labels/BUILD b/test/get_labels/BUILD
new file mode 100644
index 000000000..e7c03693b
--- /dev/null
+++ b/test/get_labels/BUILD
@@ -0,0 +1,109 @@
+subinclude("//test/build_defs")
+
+def maxdepth_test(name:str, deps:list=None, maxdepth:int, expected:list):
+ test_case = genrule(
+ name = name,
+ outs = [f"{name}.sh"],
+ cmd = {
+ "opt": "echo '#!/bin/sh' > $OUTS",
+ },
+ binary = True,
+ labels = [
+ f"name:{name}",
+ "manual",
+ ],
+ output_is_complete = False,
+ pre_build = echo_name_labels_up_to(maxdepth),
+ deps = deps,
+ )
+ expected = text_file(
+ name = tag(name, "expected"),
+ content = "\n".join(expected) + "\n",
+ )
+ plz_e2e_test(
+ name = f"{name}_test",
+ cmd = f"plz run //test/get_labels:{name}",
+ expected_output = expected,
+ )
+
+def dep(name:str, deps:list=None):
+ return genrule(
+ name = name,
+ outs = [name],
+ cmd = "touch $OUTS",
+ labels = [
+ f"name:{name}",
+ "manual",
+ ],
+ output_is_complete = False,
+ deps = deps,
+ )
+
+def echo_name_labels_up_to(maxdepth:int):
+ def echo(name:str):
+ labels = get_labels(name, "name:", maxdepth=maxdepth)
+ set_command(name, "opt", " && ".join([get_command(name, "opt")] + [f"echo 'echo {l}' >> $OUTS" for l in labels]))
+ return echo
+
+dep(name = "dep1", deps = [":dep3"])
+dep(name = "dep2")
+dep(name = "dep3", deps = [":dep4", ":dep5"])
+dep(name = "dep4")
+dep(name = "dep5")
+
+maxdepth_test(
+ name = "target_only",
+ deps = [
+ ":dep1",
+ ":dep2",
+ ],
+ maxdepth = 0,
+ expected = ["target_only"],
+)
+
+maxdepth_test(
+ name = "direct_deps",
+ deps = [
+ ":dep1",
+ ":dep2",
+ ],
+ maxdepth = 1,
+ expected = [
+ "dep1",
+ "dep2",
+ "direct_deps",
+ ],
+)
+
+maxdepth_test(
+ name = "second_level_deps",
+ deps = [
+ ":dep1",
+ ":dep2",
+ ":dep3",
+ ],
+ maxdepth = 2,
+ expected = [
+ "dep1",
+ "dep2",
+ "dep3",
+ "second_level_deps",
+ ],
+)
+
+maxdepth_test(
+ name = "all_deps",
+ deps = [
+ ":dep1",
+ ":dep2",
+ ],
+ maxdepth = -1,
+ expected = [
+ "all_deps",
+ "dep1",
+ "dep2",
+ "dep3",
+ "dep4",
+ "dep5",
+ ],
+)
diff --git a/test/runtime_deps/BUILD b/test/runtime_deps/BUILD
new file mode 100644
index 000000000..8f07efff6
--- /dev/null
+++ b/test/runtime_deps/BUILD
@@ -0,0 +1,101 @@
+subinclude("//test/build_defs")
+
+please_repo_e2e_test(
+ name = "runtime_deps_test",
+ plz_command = " && ".join([
+ "plz test //test:runtime_deps_test_case",
+ ]),
+ repo = "repo",
+)
+
+# Ensure that the runtime_deps field is correctly printed for those that have one (and that it is omitted for those that
+# don't). Importantly, ensure that run-time dependencies are not printed transitively - `plz query print` should only
+# print the target's direct run-time dependencies.
+please_repo_e2e_test(
+ name = "query_print_test",
+ plz_command = " && ".join([
+ "plz query print -f runtime_deps //test:runtime_deps_test_case > runtime_deps_test_case",
+ "plz query print -f runtime_deps //test:target_with_no_runtime_deps > target_with_no_runtime_deps",
+ ]),
+ expected_output = {
+ "runtime_deps_test_case": "//test:target_with_runtime_deps",
+ "target_with_no_runtime_deps": "",
+ },
+ repo = "repo",
+)
+
+# Ensure that run-time dependencies are in fact considered dependencies by `plz query deps`. Run-time dependencies added
+# by a call to the add_runtime_dep function in a post-build function should not appear here.
+_expected_deps = """\
+//test:dep_with_runtime_dep
+ //test:dep_runtime_dep
+//test:src_with_runtime_dep
+ //test:src_dep_with_runtime_dep
+ //test:src_dep_runtime_dep
+ //test:src_runtime_dep
+//test:target_with_runtime_deps
+ //test:runtime_dep
+ //test:target_requiring_kittens
+ //test:target_with_provides_runtime_dep
+ //test:provides_runtime_dep
+ //test:target_with_another_runtime_dep
+ //test:another_runtime_dep
+ //test:target_with_build_and_runtime_deps
+ //test:build_and_runtime_dep
+ //test:target_with_post_build_runtime_dep
+//test:test_data_with_runtime_dep
+ //test:test_data_runtime_dep
+//test:test_tool_with_runtime_dep
+ //test:test_tool_runtime_dep"""
+
+please_repo_e2e_test(
+ name = "query_deps_test",
+ plz_command = "plz query deps //test:runtime_deps_test_case > deps",
+ expected_output = {
+ "deps": _expected_deps,
+ },
+ repo = "repo",
+)
+
+# Ensure that run-time dependencies are in fact considered dependencies by `plz query revdeps`.
+please_repo_e2e_test(
+ name = "query_revdeps_test",
+ plz_command = "plz query revdeps //test:another_runtime_dep > revdeps",
+ expected_output = {
+ "revdeps": "//test:target_with_another_runtime_dep",
+ },
+ repo = "repo",
+)
+
+please_repo_e2e_test(
+ name = "runtime_deps_from_srcs_test",
+ plz_command = "plz test //from_srcs_test:runtime_deps_from_srcs_test_case",
+ repo = "repo",
+)
+
+please_repo_e2e_test(
+ name = "runtime_deps_from_data_test",
+ plz_command = " && ".join([
+ "plz test //from_data_test:runtime_deps_from_data_test_case",
+ "plz -c dbg test //from_data_test:runtime_deps_from_data_test_case",
+ ]),
+ repo = "repo",
+)
+
+please_repo_e2e_test(
+ name = "runtime_deps_from_deps_test",
+ plz_command = "plz test //from_deps_test:runtime_deps_from_deps_test_case",
+ repo = "repo",
+)
+
+please_repo_e2e_test(
+ name = "runtime_deps_from_exported_deps_test",
+ plz_command = "plz test //from_exported_deps_test:runtime_deps_from_exported_deps_test_case",
+ repo = "repo",
+)
+
+please_repo_e2e_test(
+ name = "filegroup_test",
+ plz_command = "plz test //filegroup_test:filegroup_test_case",
+ repo = "repo",
+)
diff --git a/test/runtime_deps/repo/.plzconfig b/test/runtime_deps/repo/.plzconfig
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/runtime_deps/repo/build_defs/BUILD_FILE b/test/runtime_deps/repo/build_defs/BUILD_FILE
new file mode 100644
index 000000000..d8f94687e
--- /dev/null
+++ b/test/runtime_deps/repo/build_defs/BUILD_FILE
@@ -0,0 +1,5 @@
+export_file(
+ name = "test",
+ src = "test.build_defs",
+ visibility = ["PUBLIC"],
+)
diff --git a/test/runtime_deps/repo/build_defs/test.build_defs b/test/runtime_deps/repo/build_defs/test.build_defs
new file mode 100644
index 000000000..2f9e725fd
--- /dev/null
+++ b/test/runtime_deps/repo/build_defs/test.build_defs
@@ -0,0 +1,45 @@
+def exists(file:str):
+ path = join_path(package_name(), file)
+ return f"echo -n '{path} exists: '; if [ -f '{path}' ]; then echo OK; else echo FAIL; exit 1; fi"
+
+def not_exists(file:str):
+ path = join_path(package_name(), file)
+ return f"echo -n '{path} does not exist: '; if [ -f '{path}' ]; then echo FAIL; exit 1; else echo OK; fi"
+
+def target(name:str, srcs:list=[], build_tests:list=[], data:list=[], debug_data:list=[], post_build:function=None,
+ requires:list=None, provides:dict=None, runtime_deps:list=[], runtime_deps_from_srcs:bool=False,
+ runtime_deps_from_deps:bool=False, exported_deps:list=[], deps:list=[]):
+ # Ensure that run-time dependencies are not present at build time, unless they are also
+ # explicitly given as build-time dependencies. (The lstrips here turn build target names
+ # into the names of files they output - they're slightly grungy, but they allow us to
+ # re-use exists and not_exists outside of this function; they work because targets
+ # generated by runtime_dep output a single file who name is identical to that of the
+ # target's.)
+ cmd = [not_exists(r.lstrip(":")) for r in runtime_deps if r not in deps]
+ cmd += [exists(d.lstrip(":")) for d in deps]
+ return build_rule(
+ name = name,
+ srcs = srcs,
+ outs = [name],
+ # Assume that if a post-build function is defined, it dynamically adds a run-time
+ # dependency, which requires this target to be marked as binary.
+ binary = len(runtime_deps) > 0 or post_build is not None,
+ cmd = " && ".join(cmd + build_tests + ["touch $OUTS"]),
+ data = data,
+ debug_data = debug_data,
+ post_build = post_build,
+ requires = requires,
+ provides = provides,
+ runtime_deps = runtime_deps,
+ runtime_deps_from_srcs = runtime_deps_from_srcs,
+ runtime_deps_from_deps = runtime_deps_from_deps,
+ exported_deps = exported_deps,
+ deps = deps,
+ )
+
+def runtime_dep(name:str):
+ return genrule(
+ name = name,
+ outs = [name],
+ cmd = "touch $OUTS",
+ )
diff --git a/test/runtime_deps/repo/filegroup_test/BUILD_FILE b/test/runtime_deps/repo/filegroup_test/BUILD_FILE
new file mode 100644
index 000000000..19083d918
--- /dev/null
+++ b/test/runtime_deps/repo/filegroup_test/BUILD_FILE
@@ -0,0 +1,27 @@
+subinclude("//build_defs:test")
+
+filegroup(
+ name = "filegroup",
+ srcs = [":filegroup_src"],
+)
+
+target(
+ name = "filegroup_src",
+ runtime_deps = [":filegroup_src_runtime_dep"],
+)
+
+runtime_dep("filegroup_src_runtime_dep")
+
+gentest(
+ name = "filegroup_test_case",
+ outs = ["filegroup_test_case"],
+ cmd = [
+ not_exists("filegroup_src_runtime_dep"),
+ "touch $OUTS",
+ ],
+ data = [":filegroup"],
+ no_test_output = True,
+ test_cmd = [
+ exists("filegroup_src_runtime_dep"),
+ ],
+)
diff --git a/test/runtime_deps/repo/from_data_test/BUILD_FILE b/test/runtime_deps/repo/from_data_test/BUILD_FILE
new file mode 100644
index 000000000..27eaaa871
--- /dev/null
+++ b/test/runtime_deps/repo/from_data_test/BUILD_FILE
@@ -0,0 +1,42 @@
+subinclude("//build_defs:test")
+
+target(
+ name = "runtime_deps_from_data",
+ data = [":data_with_runtime_dep"],
+ debug_data = [":debug_data_with_runtime_dep"],
+)
+
+target(
+ name = "data_with_runtime_dep",
+ runtime_deps = [":data_runtime_dep"],
+)
+
+target(
+ name = "debug_data_with_runtime_dep",
+ runtime_deps = [":debug_data_runtime_dep"],
+)
+
+runtime_dep("data_runtime_dep")
+runtime_dep("debug_data_runtime_dep")
+
+gentest(
+ name = "runtime_deps_from_data_test_case",
+ outs = ["runtime_deps_from_data_test_case"],
+ cmd = [
+ not_exists("data_runtime_dep"),
+ not_exists("debug_data_runtime_dep"),
+ "touch $OUTS",
+ ],
+ data = [":runtime_deps_from_data"],
+ no_test_output = True,
+ test_cmd = {
+ "opt": " && ".join([
+ exists("data_runtime_dep"),
+ not_exists("debug_data_runtime_dep"),
+ ]),
+ "dbg": " && ".join([
+ exists("data_runtime_dep"),
+ exists("debug_data_runtime_dep"),
+ ]),
+ },
+)
diff --git a/test/runtime_deps/repo/from_deps_test/BUILD_FILE b/test/runtime_deps/repo/from_deps_test/BUILD_FILE
new file mode 100644
index 000000000..6c2e3fe4a
--- /dev/null
+++ b/test/runtime_deps/repo/from_deps_test/BUILD_FILE
@@ -0,0 +1,28 @@
+subinclude("//build_defs:test")
+
+target(
+ name = "runtime_deps_from_deps",
+ runtime_deps_from_deps = True,
+ deps = [":dep"],
+)
+
+target(
+ name = "dep",
+ runtime_deps = [":dep_runtime_dep"],
+)
+
+runtime_dep("dep_runtime_dep")
+
+gentest(
+ name = "runtime_deps_from_deps_test_case",
+ outs = ["runtime_deps_from_deps_test_case"],
+ cmd = [
+ not_exists("dep_runtime_dep"),
+ "touch $OUTS",
+ ],
+ data = [":runtime_deps_from_deps"],
+ no_test_output = True,
+ test_cmd = [
+ exists("dep_runtime_dep"),
+ ],
+)
diff --git a/test/runtime_deps/repo/from_exported_deps_test/BUILD_FILE b/test/runtime_deps/repo/from_exported_deps_test/BUILD_FILE
new file mode 100644
index 000000000..bd6121a4b
--- /dev/null
+++ b/test/runtime_deps/repo/from_exported_deps_test/BUILD_FILE
@@ -0,0 +1,45 @@
+subinclude("//build_defs:test")
+
+target(
+ name = "runtime_deps_from_deps",
+ runtime_deps_from_deps = True,
+ deps = [":dep_with_exported_dep"],
+)
+
+target(
+ name = "dep_with_exported_dep",
+ exported_deps = [":exported_dep"],
+ deps = [":dep"],
+)
+
+target(
+ name = "exported_dep",
+ runtime_deps = [":exported_dep_runtime_dep"],
+)
+
+target(
+ name = "dep",
+ runtime_deps = [":dep_runtime_dep"],
+)
+
+runtime_dep("exported_dep_runtime_dep")
+runtime_dep("dep_runtime_dep")
+
+gentest(
+ name = "runtime_deps_from_exported_deps_test_case",
+ outs = ["runtime_deps_from_exported_deps_test_case"],
+ cmd = [
+ not_exists("dep_runtime_dep"),
+ not_exists("exported_dep_runtime_dep"),
+ "touch $OUTS",
+ ],
+ data = [":runtime_deps_from_deps"],
+ no_test_output = True,
+ test_cmd = [
+ # This still shouldn't exist - it's a build-time dependency of a build-time dependency, not a run-time
+ # dependency of a build-time dependency.
+ not_exists("dep_runtime_dep"),
+ # This one should exist - it's a run-time dependency of an (exported) build-time dependency.
+ exists("exported_dep_runtime_dep"),
+ ],
+)
diff --git a/test/runtime_deps/repo/from_srcs_test/BUILD_FILE b/test/runtime_deps/repo/from_srcs_test/BUILD_FILE
new file mode 100644
index 000000000..5e525f651
--- /dev/null
+++ b/test/runtime_deps/repo/from_srcs_test/BUILD_FILE
@@ -0,0 +1,28 @@
+subinclude("//build_defs:test")
+
+target(
+ name = "runtime_deps_from_srcs",
+ srcs = [":src"],
+ runtime_deps_from_srcs = True,
+)
+
+target(
+ name = "src",
+ runtime_deps = [":src_runtime_dep"],
+)
+
+runtime_dep("src_runtime_dep")
+
+gentest(
+ name = "runtime_deps_from_srcs_test_case",
+ outs = ["runtime_deps_from_srcs_test_case"],
+ cmd = [
+ not_exists("src_runtime_dep"),
+ "touch $OUTS",
+ ],
+ data = [":runtime_deps_from_srcs"],
+ no_test_output = True,
+ test_cmd = [
+ exists("src_runtime_dep"),
+ ],
+)
diff --git a/test/runtime_deps/repo/test/BUILD_FILE b/test/runtime_deps/repo/test/BUILD_FILE
new file mode 100644
index 000000000..c33f48cb6
--- /dev/null
+++ b/test/runtime_deps/repo/test/BUILD_FILE
@@ -0,0 +1,159 @@
+subinclude("//build_defs:test")
+
+target(
+ name = "target_with_no_runtime_deps",
+)
+
+target(
+ name = "target_with_runtime_deps",
+ build_tests = [
+ # Ensure that the run-time dependencies :target_with_post_build_runtime_dep and
+ # :target_requiring_kittens are not present at build time (these ones are tricky
+ # to identify statically, hence explicitly listing them here).
+ not_exists("post_build_runtime_dep"),
+ not_exists("provides_runtime_dep"),
+ ],
+ runtime_deps = [
+ ":runtime_dep",
+ ":target_with_another_runtime_dep",
+ ":target_with_build_and_runtime_deps",
+ ":target_with_post_build_runtime_dep",
+ ":target_requiring_kittens",
+ ],
+)
+
+target(
+ name = "target_with_another_runtime_dep",
+ runtime_deps = [":another_runtime_dep"],
+)
+
+target(
+ name = "target_with_build_and_runtime_deps",
+ runtime_deps = [":build_and_runtime_dep"],
+ deps = [":build_and_runtime_dep"],
+)
+
+target(
+ name = "target_with_post_build_runtime_dep",
+ build_tests = [
+ # Ensure that the run-time dependency added by the post-build function is not present at
+ # build time (this one can't be identified statically, hence explicitly listing it here).
+ not_exists("post_build_runtime_dep"),
+ ],
+ post_build = lambda name, _: add_runtime_dep(name, ":post_build_runtime_dep"),
+)
+
+target(
+ name = "src_with_runtime_dep",
+ runtime_deps = [":src_runtime_dep"],
+ deps = [":src_dep_with_runtime_dep"],
+)
+
+target(
+ name = "src_dep_with_runtime_dep",
+ runtime_deps = [":src_dep_runtime_dep"],
+)
+
+target(
+ name = "dep_with_runtime_dep",
+ runtime_deps = [":dep_runtime_dep"],
+)
+
+target(
+ name = "target_requiring_kittens",
+ build_tests = [
+ # Ensure that neither of the run-time dependencies that could possibly be provided by
+ # :runtime_dep_providing_kittens or :target_with_provides_runtime_dep are present at
+ # build time (only the latter *should* be provided, but it isn't possible to generate
+ # both of these tests statically, hence explicitly listing them here).
+ not_exists("runtime_dep_providing_kittens_runtime_dep"),
+ not_exists("provides_runtime_dep"),
+ ],
+ requires = ["kittens"],
+ runtime_deps = [":runtime_dep_providing_kittens"],
+)
+
+target(
+ name = "runtime_dep_providing_kittens",
+ provides = {
+ "kittens": ":target_with_provides_runtime_dep",
+ },
+ runtime_deps = [":runtime_dep_providing_kittens_runtime_dep"],
+)
+
+target(
+ name = "target_with_provides_runtime_dep",
+ runtime_deps = [":provides_runtime_dep"],
+)
+
+target(
+ name = "test_data_with_runtime_dep",
+ runtime_deps = [":test_data_runtime_dep"],
+)
+
+target(
+ name = "test_tool_with_runtime_dep",
+ runtime_deps = [":test_tool_runtime_dep"],
+)
+
+runtime_dep("runtime_dep")
+runtime_dep("another_runtime_dep")
+runtime_dep("build_and_runtime_dep")
+runtime_dep("post_build_runtime_dep")
+runtime_dep("src_runtime_dep")
+runtime_dep("src_dep_runtime_dep")
+runtime_dep("dep_runtime_dep")
+runtime_dep("test_data_runtime_dep")
+runtime_dep("test_tool_runtime_dep")
+runtime_dep("provides_runtime_dep")
+runtime_dep("runtime_dep_providing_kittens_runtime_dep")
+
+gentest(
+ name = "runtime_deps_test_case",
+ srcs = {
+ "src_with_runtime_dep": [":src_with_runtime_dep"],
+ },
+ outs = ["runtime_deps_test_case"],
+ cmd = [
+ # Ensure this target's sources' and dependencies' run-time dependencies are present at build time...
+ exists("src_runtime_dep"),
+ exists("dep_runtime_dep"),
+ # ...but that those targets' (build-time) dependencies' run-time dependencies are not...
+ not_exists("src_dep_runtime_dep"),
+ # ...and that this target's run-time dependencies are not...
+ not_exists("runtime_dep"),
+ not_exists("another_runtime_dep"),
+ not_exists("build_and_runtime_dep"),
+ not_exists("post_build_runtime_dep"),
+ not_exists("provides_runtime_dep"),
+ not_exists("runtime_dep_providing_kittens_runtime_dep"),
+ # ...and that this target's test-time dependencies are not.
+ not_exists("test_data_runtime_dep"),
+ not_exists("test_tool_runtime_dep"),
+ "touch $OUTS",
+ ],
+ data = [":test_data_with_runtime_dep"],
+ no_test_output = True,
+ runtime_deps = [":target_with_runtime_deps"],
+ test_cmd = [
+ # Ensure this target's sources' and dependencies' (transitive) run-time dependencies are not present at test time...
+ not_exists("src_runtime_dep"),
+ not_exists("dep_runtime_dep"),
+ not_exists("src_dep_runtime_dep"),
+ # ...but that this target's run-time dependencies are...
+ exists("runtime_dep"),
+ exists("another_runtime_dep"),
+ exists("build_and_runtime_dep"),
+ exists("post_build_runtime_dep"),
+ exists("provides_runtime_dep"),
+ # ...and that this target's test-time dependencies are...
+ exists("test_data_runtime_dep"),
+ exists("test_tool_runtime_dep"),
+ # ...and that the requires/provides in the run-time dependency tree were correctly resolved.
+ not_exists("runtime_dep_providing_kittens_runtime_dep"),
+ ],
+ test_tools = [":test_tool_with_runtime_dep"],
+ deps = [
+ ":dep_with_runtime_dep",
+ ],
+)
diff --git a/third_party/go/BUILD b/third_party/go/BUILD
index f42164f98..e4a905761 100644
--- a/third_party/go/BUILD
+++ b/third_party/go/BUILD
@@ -5,14 +5,14 @@ package(default_visibility = ["PUBLIC"])
go_toolchain(
name = "toolchain",
hashes = [
- "addbfce2056744962e2d7436313ab93486660cf7a2e066d171b9d6f2da7c7abe", # go1.24.1.darwin-amd64.tar.gz
- "295581b5619acc92f5106e5bcb05c51869337eb19742fdfa6c8346c18e78ff88", # go1.24.1.darwin-arm64.tar.gz
- "47d7de8bb64d5c3ee7b6723aa62d5ecb11e3568ef2249bbe1d4bbd432d37c00c", # go1.24.1.freebsd-amd64.tar.gz
- "cb2396bae64183cdccf81a9a6df0aea3bce9511fc21469fb89a0c00470088073", # go1.24.1.linux-amd64.tar.gz
- "8df5750ffc0281017fb6070fba450f5d22b600a02081dceef47966ffaf36a3af", # go1.24.1.linux-arm64.tar.gz
+ "fde05d84f7f64c8d01564f299ea1897fe94457d20d8d9054200ac1f8ae1c2bc3", # go1.24.10.darwin-amd64.tar.gz
+ "71c70841bcdadf4b5d2f7c0f099952907969f25235663622a47d6f2233ad39aa", # go1.24.10.darwin-arm64.tar.gz
+ "cb917b64aa4a407ed3310b397cc4dca10f0a3e2b0dd184ed74164ceaeab2625e", # go1.24.10.freebsd-amd64.tar.gz
+ "dd52b974e3d9c5a7bbfb222c685806def6be5d6f7efd10f9caa9ca1fa2f47955", # go1.24.10.linux-amd64.tar.gz
+ "94a99dae43dab8a3fe337485bbb89214b524285ec53ea02040514b0c2a9c3f94", # go1.24.10.linux-arm64.tar.gz
],
install_std = False,
- version = "1.24.1",
+ version = "1.24.10",
)
go_stdlib(
@@ -35,12 +35,12 @@ go_repo(
go_repo(
module = "golang.org/x/text",
- version = "v0.23.0",
+ version = "v0.31.0",
)
go_repo(
module = "golang.org/x/tools",
- version = "v0.21.1-0.20240508182429-e35e4ccd0d2d",
+ version = "v0.38.0",
)
go_repo(
@@ -116,7 +116,7 @@ go_repo(
go_repo(
module = "golang.org/x/net",
- version = "v0.38.0",
+ version = "v0.47.0",
)
go_repo(
@@ -211,7 +211,7 @@ go_repo(
go_repo(
module = "golang.org/x/term",
- version = "v0.30.0",
+ version = "v0.37.0",
)
go_repo(
@@ -271,7 +271,7 @@ go_repo(
go_repo(
module = "golang.org/x/sync",
- version = "v0.12.0",
+ version = "v0.18.0",
)
go_repo(
@@ -331,7 +331,7 @@ go_repo(
go_repo(
module = "golang.org/x/sys",
- version = "v0.31.0",
+ version = "v0.38.0",
)
go_repo(
@@ -401,7 +401,7 @@ go_repo(
go_repo(
module = "golang.org/x/crypto",
- version = "v0.36.0",
+ version = "v0.45.0",
)
go_repo(
diff --git a/tools/build_langserver/lsp/definition_test.go b/tools/build_langserver/lsp/definition_test.go
index ff05ee0af..a728a8ca7 100644
--- a/tools/build_langserver/lsp/definition_test.go
+++ b/tools/build_langserver/lsp/definition_test.go
@@ -25,7 +25,7 @@ func TestDefinition(t *testing.T) {
assert.Equal(t, []lsp.Location{
{
URI: lsp.DocumentURI("file://" + filepath.Join(cacheDir, "please/misc_rules.build_defs")),
- Range: xrng(3, 0, 145, 5),
+ Range: xrng(3, 0, 191, 5),
},
}, locs)
}
@@ -45,7 +45,7 @@ func TestDefinitionStatement(t *testing.T) {
assert.Equal(t, []lsp.Location{
{
URI: lsp.DocumentURI("file://" + filepath.Join(cacheDir, "please/misc_rules.build_defs")),
- Range: xrng(3, 0, 145, 5),
+ Range: xrng(3, 0, 191, 5),
},
}, locs)
}
@@ -65,7 +65,7 @@ func TestDefinitionBuiltin(t *testing.T) {
assert.Equal(t, []lsp.Location{
{
URI: lsp.DocumentURI("file://" + filepath.Join(cacheDir, "please/misc_rules.build_defs")),
- Range: xrng(3, 0, 145, 5),
+ Range: xrng(3, 0, 191, 5),
},
}, locs)
}