Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions .github/workflows/test-mlc-script-features.yml
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,43 @@ jobs:
chmod +x "$REPO_SCRIPT_DIR/test-posthookdep-failure/run.sh"
printf '@echo off\necho Main script runs\n' > "$REPO_SCRIPT_DIR/test-posthookdep-failure/run.bat"

# Child script used for version_persistent conflict test
mkdir -p "$REPO_SCRIPT_DIR/test-version-persistent-child"
cat > "$REPO_SCRIPT_DIR/test-version-persistent-child/meta.yaml" << 'EOF'
alias: test-version-persistent-child
automation_alias: script
automation_uid: 5b4e0237da074764
tags:
- test
- version-persistent-child
uid: aaaa5555bbbb6666
EOF
printf '#!/bin/bash\necho "Version persistent child"\n' > "$REPO_SCRIPT_DIR/test-version-persistent-child/run.sh"
chmod +x "$REPO_SCRIPT_DIR/test-version-persistent-child/run.sh"
printf '@echo off\necho Version persistent child\n' > "$REPO_SCRIPT_DIR/test-version-persistent-child/run.bat"

# Parent script that calls the same dependency script with conflicting versions
mkdir -p "$REPO_SCRIPT_DIR/test-version-persistent-conflict"
cat > "$REPO_SCRIPT_DIR/test-version-persistent-conflict/meta.yaml" << 'EOF'
alias: test-version-persistent-conflict
automation_alias: script
automation_uid: 5b4e0237da074764
deps:
- tags: test,version-persistent-child
version: "1.0"
version_persistent: true
- tags: test,version-persistent-child
version: "2.0"
version_persistent: true
tags:
- test
- version-persistent-conflict
uid: aaaa6666bbbb7777
EOF
printf '#!/bin/bash\necho "This should not run due to dependency conflict"\n' > "$REPO_SCRIPT_DIR/test-version-persistent-conflict/run.sh"
chmod +x "$REPO_SCRIPT_DIR/test-version-persistent-conflict/run.sh"
printf '@echo off\necho This should not run due to dependency conflict\n' > "$REPO_SCRIPT_DIR/test-version-persistent-conflict/run.bat"

- name: Test dep script search failure
shell: bash
run: |
Expand Down Expand Up @@ -389,6 +426,17 @@ jobs:
echo "PASS: posthook_dep search failure correctly caused an error"
fi

- name: Test version_persistent conflict
shell: bash
run: |
OUTPUT=$(mlcr test,version-persistent-conflict --quiet 2>&1 || true)
if echo "$OUTPUT" | grep -qi "version_persistent conflict"; then
echo "PASS: version_persistent conflict correctly caused an error"
else
echo "ERROR: Expected version_persistent conflict but got: $OUTPUT"
exit 1
fi

test_script_options:
runs-on: ${{ matrix.os }}
strategy:
Expand Down
2 changes: 2 additions & 0 deletions automation/script/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,8 @@ adr:

> **Best Practice:** Always prefer `ad` over `adr` in `meta.yaml`. Using `adr` inside a script's meta can unintentionally change dependency versions or tags in deeply nested scripts that the author did not anticipate. Reserve `adr` for **CLI overrides** where the user explicitly intends a global effect.

You can also set `version_persistent: true` on a dependency override. When enabled, MLC remembers the first resolved version for that dependency script (by script ID) and raises an error if the same dependency script is later resolved to a different version in the same workflow execution.

---

## Environment Export Control (`new_env_keys` / `new_state_keys`)
Expand Down
39 changes: 39 additions & 0 deletions automation/script/module.py
Original file line number Diff line number Diff line change
Expand Up @@ -3423,6 +3423,45 @@ def replacer(match):
else:
return r

if is_true(d.get('version_persistent')):
dep_script_id = new_run_state.get('script_id', '')
dep_script_uid = ''
if dep_script_id:
dep_script_uid = dep_script_id.split(
',')[-1].strip()
dep_version = ''

dep_version_info = new_run_state.get(
'version_info', [])
if dep_script_uid and dep_version_info:
for dep_info in reversed(dep_version_info):
if not isinstance(
dep_info, dict) or not dep_info:
continue
dep_key = next(iter(dep_info))
dep_data = dep_info.get(dep_key, {})
if dep_data.get('script_uid',
'') == dep_script_uid:
dep_version = str(
dep_data.get('version', '')).strip()
break

if not dep_version:
dep_version = str(d.get('version', '')).strip()

if dep_script_id and dep_version:
version_persistent = self.const_state.setdefault(
'mlc_dependency_version_persistent', {})
existing_dep_version = str(
version_persistent.get(dep_script_id, '')).strip()

if existing_dep_version and existing_dep_version != dep_version:
return {'return': 1, 'error': 'version_persistent conflict for dependency "{}" (tags: {}): expected version "{}" but got "{}"'.format(
dep_script_id, d.get('tags', ''), existing_dep_version, dep_version)}

version_persistent.setdefault(
dep_script_id, dep_version)

run_state['version_info'] = new_run_state.get(
'version_info')

Expand Down
Loading