Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use HEAD versions in presubmit CI #782

Closed
wants to merge 12 commits into from
24 changes: 2 additions & 22 deletions .github/container/bump.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,13 @@

usage() {
cat <<EOF
This script is a utility for updating source references in a manifest YAML file for building
JAX-Toolbox images. It either updates the 'latest_verified_commit' for each package in the
manifest based on its current tracking reference, or, if specified, creates local patches that
freeze git-refs (which can point to different SHAs).
This script is a utility for updating patches when building JAX-Toolbox images.

Usage: $0 [OPTION]...
-b, --base-patch-dir PATH Where generated patch files are written. Default is $SCRIPT_DIR/patches
-i, --input-manifest PATH The YAML manifest file specifying the world state. Updated in-place unless --output-manifest is provided
-h, --help Print usage.
-o, --output-manifest PATH Path to output manifest. Use this if you don't want to update manifest in-place
-s, --skip-bump-refs If provided, update patch files and the patchlist in the manifest, but skip bumping refs

Note: patches are always updated in-place

Expand All @@ -35,10 +31,6 @@ while [ : ]; do
BASE_PATCH_DIR=$(readlink -f "$2")
shift 2
;;
-s | --skip-bump-refs)
SKIP_BUMP_REFS=1
shift 1
;;
-i | --input-manifest)
MANIFEST_IN=$(readlink -f "$2")
shift 2
Expand Down Expand Up @@ -68,7 +60,6 @@ set -eou pipefail
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )

BASE_PATCH_DIR=${BASE_PATCH_DIR:-$SCRIPT_DIR/patches}
SKIP_BUMP_REFS=${SKIP_BUMP_REFS:-0}

if [[ -z "${MANIFEST_IN:-}" ]]; then
echo "Need to provide a value for -i/--input-manifest"
Expand All @@ -84,19 +75,8 @@ else
fi

for pkg in $(yq e 'keys | .[]' $MANIFEST_OUT); do
mode=$(yq e ".${pkg}.mode" $MANIFEST_OUT)
if [[ $mode == git-clone || $mode == pip-vcs ]] && [[ $SKIP_BUMP_REFS -eq 0 ]]; then
url=$(yq e ".${pkg}.url" $MANIFEST_OUT)
tracking_ref=$(yq e ".${pkg}.tracking_ref" $MANIFEST_OUT)
if ! new_ref=$(git ls-remote --exit-code $url $tracking_ref | awk '{print $1}'); then
echo "Could not fetch $tracking_ref from $url"
exit 1
fi
yq e ".${pkg}.latest_verified_commit = \"$new_ref\"" -i $MANIFEST_OUT
fi

has_patches=$(yq e ".${pkg} | has(\"patches\")" $MANIFEST_OUT)
if [[ $mode == git-clone && $has_patches == "true" ]]; then
if [[ $has_patches == "true" ]]; then
url=$(yq e ".${pkg}.url" $MANIFEST_OUT)
repo_tmp=$(mktemp -d /tmp/${pkg}.XXXXXX)
git clone $url $repo_tmp
Expand Down
2 changes: 1 addition & 1 deletion .github/container/create-distribution.sh
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ done
git fetch origin $TRACKING_REF

# previous-HEAD's purpose is to point to the state of the repo before any distribution changes are made
# We do not rely on the manifest.yaml's .${library}.latest_verified_commit because local commits may be made on top by the upstream docker builds
# We do not rely on the manifest.yaml's .${library}.commit because local commits may be made on top by the upstream docker builds
if ! git rev-parse --verify previous-HEAD >/dev/null 2>&1; then
echo "[INFO]: Basing distribution on HEAD ($(git rev-parse HEAD)) and marking that with the local branch: previous-HEAD"
git branch --force previous-HEAD HEAD
Expand Down
2 changes: 1 addition & 1 deletion .github/container/git-clone.sh
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,4 @@ popd
mkdir -p $(dirname ${MANIFEST})
touch ${MANIFEST}
PACKAGE=$(basename "${DESTINATION}")
yq eval --inplace ". += {\"${PACKAGE}\": {\"url\": \"${GIT_REPO}\", \"tracking_ref\": \"${GIT_REF}\", \"latest_verified_commit\": \"${COMMIT_SHA}\", \"mode\": \"git-clone\"}}" ${MANIFEST}
yq eval --inplace ". += {\"${PACKAGE}\": {\"url\": \"${GIT_REPO}\", \"tracking_ref\": \"${GIT_REF}\", \"commit\": \"${COMMIT_SHA}\", \"mode\": \"git-clone\"}}" ${MANIFEST}
132 changes: 1 addition & 131 deletions .github/container/manifest.yaml
Original file line number Diff line number Diff line change
@@ -1,32 +1,14 @@
jax:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct me if I'm wrong, but you suggest to use default values for jax ref#commit from Dockerfile rather than from manifest file?
What is the scenario for NGC release? Update Dockerfiles accordingly?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely yes for the normal CI: by default we take the Dockerfile defaults, which is basically "HEAD".

When preparing releases, one option (noted here: #782 (comment)) is to commit a manifest file to the release branch and use that. Your suggestion of changing the default values in the Docker files in the release branches would also work, I think, and would require less machinery (to translate manifest --> URLREF build arg) but might be a little more tedious to seed at the beginning of the release process.

cc: @terrykong @ko3n1g

url: https://github.com/google/jax.git
tracking_ref: main
latest_verified_commit: 8f4658ecdbe40cde0c43a9ab127359347943c076
mode: git-clone
xla:
url: https://github.com/openxla/xla.git
tracking_ref: main
latest_verified_commit: 6de79c2c931374dfa7e403c0626d8dd1ec0ed398
mode: git-clone
flax:
url: https://github.com/google/flax.git
mirror_url: https://github.com/nvjax-svc-0/flax.git
tracking_ref: main
latest_verified_commit: 718aa8ccb12c3fdefcf3d196874e4fc667b3ade5
mode: git-clone
# Patches are only used for rosetta t5x
patches:
pull/3340/head: file://patches/flax/PR-3340.patch # Add Sharding Annotations to Flax Modules
transformer-engine:
url: https://github.com/NVIDIA/TransformerEngine.git
tracking_ref: main
latest_verified_commit: 1ec33ae1191ae6644365155f8e8f618145c44cd7
mode: git-clone
t5x:
url: https://github.com/google-research/t5x.git
mirror_url: https://github.com/nvjax-svc-0/t5x.git
tracking_ref: main
latest_verified_commit: 707995a3a8238e0c3557d3cc1318a883215c54c9
mode: git-clone
patches:
mirror/patch/partial-checkpoint-restore: file://patches/t5x/mirror-patch-partial-checkpoint-restore.patch # pull/1392/head # https://github.com/google-research/t5x/pull/1392: Add support for partial checkpoint restore
mirror/patch/dali-support: file://patches/t5x/mirror-patch-dali-support.patch # pull/1393/head # https://github.com/google-research/t5x/pull/1393: Adds DALI support to t5x
Expand All @@ -35,124 +17,12 @@ paxml:
url: https://github.com/google/paxml.git
mirror_url: https://github.com/nvjax-svc-0/paxml.git
tracking_ref: main
latest_verified_commit: 051795784f8ddaba57eb51218addb5f1db8e04f4
mode: git-clone
patches:
pull/46/head: file://patches/paxml/PR-46.patch # adds Transformer Engine support
praxis:
url: https://github.com/google/praxis.git
mirror_url: https://github.com/nvjax-svc-0/praxis.git
tracking_ref: main
latest_verified_commit: c58bcc4e82c80489a7f8a2c3366e7f6b32d271d4
mode: git-clone
patches:
pull/27/head: file://patches/praxis/PR-27.patch # This PR allows XLA:GPU to detect the MHA pattern more easily to call fused kernels from cublas.
pull/36/head: file://patches/praxis/PR-36.patch # adds Transformer Engine support
lingvo:
# Used only in ARM pax builds
url: https://github.com/tensorflow/lingvo.git
tracking_ref: master
latest_verified_commit: 05a076b0783a8bbf4a770095966c472bb37bbf65
mode: git-clone
tensorflow-text:
# Used only in ARM pax and t5x builds
url: https://github.com/tensorflow/text.git
tracking_ref: v2.13.0
latest_verified_commit: 917a681d7220ebf9b62a08b6f9ce7b7db886ddef
mode: git-clone
pydantic:
version: X.Y.Z
mode: pip-constraint
# Used by praxis
fiddle:
url: https://github.com/google/fiddle.git
tracking_ref: main
latest_verified_commit: 2a17618c56eb99aa58aa898ae12cbac7cf5c3b30
mode: pip-vcs
# Used by t5x
airio:
url: https://github.com/google/airio.git
tracking_ref: main
latest_verified_commit: 3e13fd16038f3f376cddd289bd10eef53a4933f4
mode: pip-vcs
clu:
url: https://github.com/google/CommonLoopUtils.git
tracking_ref: main
latest_verified_commit: c50acb760902c94a89ad3f605edc2d094bc2a7a1
mode: pip-vcs
dllogger:
url: https://github.com/NVIDIA/dllogger.git
tracking_ref: master
latest_verified_commit: 0540a43971f4a8a16693a9de9de73c1072020769
mode: pip-vcs
jestimator:
url: https://github.com/google-research/jestimator.git
tracking_ref: main
latest_verified_commit: 6a57d35539f5193a9756a7cb846654e9b221b2e7
mode: pip-vcs
optax:
url: https://github.com/google-deepmind/optax.git
tracking_ref: main
latest_verified_commit: b4acf8eed4fe26f4b7be5337a8b72cde0ffbc3cf
mode: pip-vcs
seqio:
url: https://github.com/google/seqio.git
tracking_ref: main
latest_verified_commit: 11706e4a1e01a81ea6b3e02c5ad147028d5b94bb
mode: pip-vcs
jax-triton:
url: https://github.com/jax-ml/jax-triton.git
tracking_ref: main
latest_verified_commit: 1999d9b116bf7c5c94f70de4a45b414255366fbe
mode: git-clone
maxtext:
url: https://github.com/google/maxtext.git
tracking_ref: main
latest_verified_commit: 78daad198544def8274dbd656d122fbe6a0e1129
mode: git-clone
levanter:
url: https://github.com/stanford-crfm/levanter.git
tracking_ref: main
latest_verified_commit: 19829c2c360cc1b8e7975f540e612845e4877a69
mode: git-clone
haliax:
url: https://github.com/stanford-crfm/haliax.git
tracking_ref: main
latest_verified_commit: 2a696a0c971901ff93afdaa965959d8e3b982ba9
mode: git-clone
mujoco:
url: https://github.com/google-deepmind/mujoco.git
tracking_ref: main
latest_verified_commit: e95159b4f6d48d114b16a8dc13ad26b3e44bc3e2
mode: git-clone
grain:
# Used only in ARM t5x builds
url: https://github.com/google/grain.git
tracking_ref: main
latest_verified_commit: 10600a3f5510bcb696a90e72c6e6cb1ac2bb016f
mode: git-clone
mujoco-mpc:
url: https://github.com/google-deepmind/mujoco_mpc.git
tracking_ref: main
latest_verified_commit: 4700f4a13be18398f5aaf6a33ed42e531967e3ae
mode: git-clone
language-to-reward-2023:
url: https://github.com/google-deepmind/language_to_reward_2023.git
tracking_ref: main
latest_verified_commit: abb8e5125e4ecd0da378490b73448c05a694def5
mode: git-clone
mlperf-logging:
url: https://github.com/mlcommons/logging.git
tracking_ref: master
latest_verified_commit: 99ba37ac267c870d7c6c17e1837aa9180a37cdc1
mode: pip-vcs
equinox:
url: https://github.com/patrick-kidger/equinox.git
tracking_ref: main
latest_verified_commit: 1e601672d38d2c4d483535070a3572d8e8508a20
mode: git-clone
grok-1:
url: https://github.com/xai-org/grok-1.git
tracking_ref: main
latest_verified_commit: 7207216386e07206b2083c5c0be88db1add8e631
mode: git-clone
23 changes: 16 additions & 7 deletions .github/container/pip-finalize.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,23 @@ pushd /opt/pip-tools.d
# twice
pip-compile -o requirements.pre $(ls requirements-*.in)

IFS=$'\n'
for line in $(cat requirements.pre | egrep '^[^#].+ @ git\+' || true); do
# VCS installs are of the form "PACKAGE @ git+..."
PACKAGE=$(echo "$line" | awk '{print $1}')
ref=$(yq e ".${PACKAGE}.latest_verified_commit" ${MANIFEST_FILE})
echo "${line}@${ref}"
# Find the VCS installs, which are of the form
# PACKAGE @ git+GIT_REPO_URL
for line in $(sed -n -e 's/^\([^#].*\) @ git+\(.*\)$/\1=\2/p' requirements.pre); do
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did the regex expression change? I do understand the logic afterwards (19:27) but just not how records in requirements.pre differ such that the regrex matcher needs to be changed.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before: it was just extracting PACKAGE. Now: it's extracting the GIT_REPO_URL as well.
This is because in the case that the package is not listed in the manifest file, we need the URL to query the current HEAD.

PACKAGE="${line%=*}"
REPO_URL="${line#*=}"
ref=$(yq e ".${PACKAGE}.commit" ${MANIFEST_FILE})
if [[ "${ref}" == "null" ]]; then
# If a commit wasn't pinned in the manifest, get the latest version of the
# default branch of $REPO_URL, pin it, and write it to the manifest.
ref=$(git ls-remote --exit-code "${REPO_URL}" HEAD | awk '{ print $1 }')
touch /opt/manifest.d/pip-finalize.yaml
yq -i e ".${PACKAGE}.commit = \"${ref}\"" /opt/manifest.d/pip-finalize.yaml
yq -i e ".${PACKAGE}.mode = \"pip-vcs\"" /opt/manifest.d/pip-finalize.yaml
yq -i e ".${PACKAGE}.url = \"${REPO_URL}\"" /opt/manifest.d/pip-finalize.yaml
fi
echo "${PACKAGE} @ git+${REPO_URL}@${ref}"
done | tee requirements.vcs
unset IFS

# Second pip-compile includes one more requirements file that pins all vcs installs
# Uses a special env var to let our custom pip impl know to treat the following as
Expand Down
30 changes: 13 additions & 17 deletions .github/workflows/_ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,10 @@ jobs:
DOCKERFILE: .github/container/Dockerfile.jax
RUNNER_SIZE: large
EXTRA_BUILD_ARGS: |
URLREF_JAX=${{ fromJson(inputs.SOURCE_URLREFS).JAX }}
URLREF_XLA=${{ fromJson(inputs.SOURCE_URLREFS).XLA }}
URLREF_FLAX=${{ fromJson(inputs.SOURCE_URLREFS).FLAX }}
URLREF_TRANSFORMER_ENGINE=${{ fromJson(inputs.SOURCE_URLREFS).TRANSFORMER_ENGINE }}
${{ fromJson(inputs.SOURCE_URLREFS).JAX }}
${{ fromJson(inputs.SOURCE_URLREFS).XLA }}
${{ fromJson(inputs.SOURCE_URLREFS).FLAX }}
${{ fromJson(inputs.SOURCE_URLREFS).TRANSFORMER_ENGINE }}
secrets: inherit

build-triton:
Expand All @@ -72,7 +72,7 @@ jobs:
BASE_IMAGE: ${{ needs.build-jax.outputs.DOCKER_TAG_MEALKIT }}
CONTAINER_NAME: triton
DOCKERFILE: .github/container/Dockerfile.triton
EXTRA_BUILD_ARGS: URLREF_JAX_TRITON=${{ fromJson(inputs.SOURCE_URLREFS).JAX_TRITON }}
EXTRA_BUILD_ARGS: ${{ fromJson(inputs.SOURCE_URLREFS).JAX_TRITON }}
secrets: inherit

build-equinox:
Expand All @@ -86,8 +86,7 @@ jobs:
BASE_IMAGE: ${{ needs.build-jax.outputs.DOCKER_TAG_MEALKIT }}
CONTAINER_NAME: equinox
DOCKERFILE: .github/container/Dockerfile.equinox
EXTRA_BUILD_ARGS: |
URLREF_EQUINOX=${{ fromJson(inputs.SOURCE_URLREFS).EQUINOX }}
EXTRA_BUILD_ARGS: ${{ fromJson(inputs.SOURCE_URLREFS).EQUINOX }}
secrets: inherit

build-maxtext:
Expand All @@ -102,8 +101,7 @@ jobs:
BASE_IMAGE: ${{ needs.build-jax.outputs.DOCKER_TAG_MEALKIT }}
CONTAINER_NAME: maxtext
DOCKERFILE: .github/container/Dockerfile.maxtext.amd64
EXTRA_BUILD_ARGS: |
URLREF_MAXTEXT=${{ fromJson(inputs.SOURCE_URLREFS).MAXTEXT }}
EXTRA_BUILD_ARGS: ${{ fromJson(inputs.SOURCE_URLREFS).MAXTEXT }}
secrets: inherit

build-levanter:
Expand All @@ -118,8 +116,8 @@ jobs:
CONTAINER_NAME: levanter
DOCKERFILE: .github/container/Dockerfile.levanter
EXTRA_BUILD_ARGS: |
URLREF_LEVANTER=${{ fromJson(inputs.SOURCE_URLREFS).LEVANTER }}
URLREF_HALIAX=${{ fromJson(inputs.SOURCE_URLREFS).HALIAX }}
${{ fromJson(inputs.SOURCE_URLREFS).LEVANTER }}
${{ fromJson(inputs.SOURCE_URLREFS).HALIAX }}
secrets: inherit

build-upstream-t5x:
Expand All @@ -133,8 +131,7 @@ jobs:
BASE_IMAGE: ${{ needs.build-jax.outputs.DOCKER_TAG_MEALKIT }}
CONTAINER_NAME: upstream-t5x
DOCKERFILE: .github/container/Dockerfile.t5x.${{ inputs.ARCHITECTURE }}
EXTRA_BUILD_ARGS: |
URLREF_T5X=${{ fromJson(inputs.SOURCE_URLREFS).T5X }}
EXTRA_BUILD_ARGS: ${{ fromJson(inputs.SOURCE_URLREFS).T5X }}
secrets: inherit

build-upstream-pax:
Expand All @@ -149,8 +146,8 @@ jobs:
CONTAINER_NAME: upstream-pax
DOCKERFILE: .github/container/Dockerfile.pax.${{ inputs.ARCHITECTURE }}
EXTRA_BUILD_ARGS: |
URLREF_PAXML=${{ fromJson(inputs.SOURCE_URLREFS).PAXML }}
URLREF_PRAXIS=${{ fromJson(inputs.SOURCE_URLREFS).PRAXIS }}
${{ fromJson(inputs.SOURCE_URLREFS).PAXML }}
${{ fromJson(inputs.SOURCE_URLREFS).PRAXIS }}
secrets: inherit

build-rosetta-t5x:
Expand Down Expand Up @@ -184,8 +181,7 @@ jobs:
BASE_IMAGE: ${{ needs.build-jax.outputs.DOCKER_TAG_MEALKIT }}
CONTAINER_NAME: grok
DOCKERFILE: .github/container/Dockerfile.grok
EXTRA_BUILD_ARGS: |
URLREF_GROK_1=${{ fromJson(inputs.SOURCE_URLREFS).GROK_1 }}
EXTRA_BUILD_ARGS: ${{ fromJson(inputs.SOURCE_URLREFS).GROK_1 }}
secrets: inherit

collect-docker-tags:
Expand Down
14 changes: 9 additions & 5 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ on:
BUMP_MANIFEST:
type: boolean
description: Bump git repos in manifest.yaml to head of tree?
default: false
required: false
default: true
required: true
MERGE_BUMPED_MANIFEST:
type: boolean
description: "(used if BUMP_MANIFEST=true) If true: attempt to PR/merge manifest branch"
Expand Down Expand Up @@ -88,7 +88,11 @@ jobs:
id: manifest-branch
shell: bash -x -e {0}
run: |
BUMP_MANIFEST=${{ github.event_name == 'schedule' || inputs.BUMP_MANIFEST || 'false' }}
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
BUMP_MANIFEST="${{ inputs.BUMP_MANIFEST }}"
else
BUMP_MANIFEST="true"
fi
MERGE_BUMPED_MANIFEST=${{ github.event_name == 'schedule' || inputs.MERGE_BUMPED_MANIFEST || 'false' }}
# Prepend nightly manifest branch with "z" to make it appear at the end
if [[ "$BUMP_MANIFEST" == "true" ]]; then
Expand Down Expand Up @@ -155,15 +159,15 @@ jobs:
# converts manifest yaml to a json object of {SOFTWARE_NAME: URL#REF, ...}
urlrefs=$(
cat .github/container/manifest.yaml |\
yq -o=json 'to_entries | .[] | select(.value.mode == "git-clone") | {( .key | upcase | sub("-", "_") ): .value.url + "#" + .value.latest_verified_commit}' |\
yq -o=json 'to_entries | .[] | select(.value.mode == "git-clone") | {( .key | upcase | sub("-", "_") ): "URLREF_" + ( .key | upcase | sub("-", "_") ) + "=" + .value.url + "#" + .value.commit}' |\
jq -c -s 'add'
)
# SOURCE_OVERRIDES is a comma-separated list of package=urlref pairs
IFS=, read -ra overrides <<< "${{ inputs.SOURCE_OVERRIDES }}"
for override in "${overrides[@]}"; do
PACKAGE=$(cut -d= -f 1 <<< "${override}" | tr '[:lower:]' '[:upper:]' | tr '-' '_')
URLREF=$(cut -d= -f 2- <<< "${override}")
urlrefs=$(echo "$urlrefs" | jq -c ". + {\"$PACKAGE\": \"$URLREF\"}")
urlrefs=$(echo "$urlrefs" | jq -c ". + {\"$PACKAGE\": \"URLREF_${PACKAGE}=$URLREF\"}")
Comment on lines 165 to +170
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @olupton , can you walk me through the idea of how manifest.yaml and SOURCE_OVERRIDES are meant to be used together?

If I want to install FLAX on an older commit, I have two options: I can change the manifest.yaml, or use SOURCE_OVERRIDES. When is it recommended to use what?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the idea here is that there are two modes.

First, when you're using this machinery to test some feature or fix, you don't particularly want to commit/push to JAX-Toolbox, so you can inject your feature/fix branch using SOURCE_OVERRIDES, URLREF_XXX, etc.

Second, when you are preparing a release, you gradually want to converge on a fixed and reproducible set of versions. In that case, you would take some seed manifest.yaml output (perhaps from a nightly) and commit it to a release branch of JAX-Toolbox, and then iterate from there.

Ultimately I don't see why you would want to mix the two modes, and the second one ought to be pedantic. In mode one, you default to taking the HEAD and recording what that was. In mode two, forgetting to specify a version of something should maybe be an error.

done
echo "SOURCE_URLREFS=${urlrefs}" >> $GITHUB_OUTPUT

Comment on lines 172 to 173
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

urlrefs is currently just a mapping of $PACKAGE: $URL which we feed into the build-args. However, when we feed them into the build-args, the actual build-arg-key is missing. The mapping should be $PACKAGE: URLREF_$PACKAGE=$PACKAGE

Expand Down
Loading