Fix the PATH mangling in action.yml (#2723)
* Fix the PATH mangling in action.yml Let's put the `uv` on PATH question aside, and fix #2663. This change ensures that the PATH entries added by Github when you request `shell: bash` don't make it into the pwsh environment. It also keeps the uv binary on PATH if installed via `extras:`. Would love to get this fixed soon, then we can figure out the right approach regarding `uv` discovery. * Modify script to allow testing of the GHA action on a PR Cherry-picked from #2718 and modified so we can test the action.yml file
This commit is contained in:
+25
-21
@@ -69,49 +69,53 @@ runs:
|
|||||||
venv_path = Path(r"${{ runner.temp }}") / "cibw"
|
venv_path = Path(r"${{ runner.temp }}") / "cibw"
|
||||||
if venv_path.exists():
|
if venv_path.exists():
|
||||||
shutil.rmtree(venv_path)
|
shutil.rmtree(venv_path)
|
||||||
|
|
||||||
builder = EnvBuilder()
|
builder = EnvBuilder()
|
||||||
builder.create(venv_path)
|
builder.create(venv_path)
|
||||||
exposed_binaries = {"cibuildwheel"}
|
exposed_binaries = {"cibuildwheel"}
|
||||||
|
|
||||||
if "uv" in EXTRAS:
|
if "uv" in EXTRAS:
|
||||||
exposed_binaries.add("uv")
|
exposed_binaries.add("uv")
|
||||||
clean_bin_path = builder.bin_path.parent / f"{builder.bin_path.name}.clean"
|
clean_bin_path = builder.bin_path.parent / f"{builder.bin_path.name}.clean"
|
||||||
clean_bin_path.mkdir()
|
clean_bin_path.mkdir()
|
||||||
|
|
||||||
for path in list(builder.bin_path.iterdir()):
|
for path in list(builder.bin_path.iterdir()):
|
||||||
if path.stem in exposed_binaries:
|
if path.stem in exposed_binaries:
|
||||||
try:
|
try:
|
||||||
os.symlink(path, clean_bin_path / path.name)
|
os.symlink(path, clean_bin_path / path.name)
|
||||||
except OSError:
|
except OSError:
|
||||||
import shutil
|
|
||||||
|
|
||||||
shutil.copy2(path, clean_bin_path / path.name)
|
shutil.copy2(path, clean_bin_path / path.name)
|
||||||
full_path = f"{clean_bin_path}{os.pathsep}{os.environ['PATH']}"
|
|
||||||
|
cibw_bin = [p for p in builder.bin_path.glob("cibuildwheel*") if p.stem == "cibuildwheel"][0]
|
||||||
|
|
||||||
with open(os.environ["GITHUB_OUTPUT"], "at") as f:
|
with open(os.environ["GITHUB_OUTPUT"], "at") as f:
|
||||||
f.write(f"updated-path={full_path}\n")
|
f.write(f"cibw-bin={cibw_bin}\n")
|
||||||
|
f.write(f"prepend-path={clean_bin_path}\n")
|
||||||
|
|
||||||
print("::endgroup::")
|
print("::endgroup::")
|
||||||
EOF
|
EOF
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
# Redirecting stderr to stdout to fix interleaving issue in Actions.
|
# Redirecting stderr to stdout to fix interleaving issue in Actions.
|
||||||
- run: >
|
- run: |
|
||||||
cibuildwheel
|
export PATH="${{ steps.cibw.outputs.prepend-path }}:$PATH"
|
||||||
"${{ inputs.package-dir }}"
|
|
||||||
${{ inputs.output-dir != '' && format('--output-dir "{0}"', inputs.output-dir) || ''}}
|
"${{ steps.cibw.outputs.cibw-bin }}" \
|
||||||
${{ inputs.config-file != '' && format('--config-file "{0}"', inputs.config-file) || ''}}
|
"${{ inputs.package-dir }}" \
|
||||||
${{ inputs.only != '' && format('--only "{0}"', inputs.only) || ''}}
|
${{ inputs.output-dir != '' && format('--output-dir "{0}"', inputs.output-dir) || ''}} \
|
||||||
2>&1
|
${{ inputs.config-file != '' && format('--config-file "{0}"', inputs.config-file) || ''}} \
|
||||||
env:
|
${{ inputs.only != '' && format('--only "{0}"', inputs.only) || ''}} \
|
||||||
PATH: "${{ steps.cibw.outputs.updated-path }}"
|
2>&1
|
||||||
shell: bash
|
shell: bash
|
||||||
if: runner.os != 'Windows'
|
if: runner.os != 'Windows'
|
||||||
|
|
||||||
# Windows needs powershell to interact nicely with Meson
|
# Windows needs powershell to interact nicely with Meson
|
||||||
- run: >
|
- run: |
|
||||||
cibuildwheel
|
$env:PATH = "${{ steps.cibw.outputs.prepend-path }};$env:PATH"
|
||||||
"${{ inputs.package-dir }}"
|
& "${{ steps.cibw.outputs.cibw-bin }}" `
|
||||||
${{ inputs.output-dir != '' && format('--output-dir "{0}"', inputs.output-dir) || ''}}
|
"${{ inputs.package-dir }}" `
|
||||||
${{ inputs.config-file != '' && format('--config-file "{0}"', inputs.config-file) || ''}}
|
${{ inputs.output-dir != '' && format('--output-dir "{0}"', inputs.output-dir) || ''}} `
|
||||||
${{ inputs.only != '' && format('--only "{0}"', inputs.only) || ''}}
|
${{ inputs.config-file != '' && format('--config-file "{0}"', inputs.config-file) || ''}} `
|
||||||
env:
|
${{ inputs.only != '' && format('--only "{0}"', inputs.only) || ''}}
|
||||||
PATH: "${{ steps.cibw.outputs.updated-path }}"
|
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
if: runner.os == 'Windows'
|
if: runner.os == 'Windows'
|
||||||
|
|||||||
@@ -39,10 +39,10 @@ class CIService(typing.NamedTuple):
|
|||||||
name: str
|
name: str
|
||||||
dst_config_path: str
|
dst_config_path: str
|
||||||
badge_md: str
|
badge_md: str
|
||||||
config_file_transform: typing.Callable[[str], str] = lambda x: x # identity by default
|
config_file_transform: typing.Callable[[str, str], str] = lambda x, _: x # identity by default
|
||||||
|
|
||||||
|
|
||||||
def github_config_file_transform(content: str) -> str:
|
def github_config_file_transform(content: str, git_ref: str) -> str:
|
||||||
# one of the the github configs only builds on main, so we need to remove that restriction
|
# one of the the github configs only builds on main, so we need to remove that restriction
|
||||||
# so our example build will run on the test branch.
|
# so our example build will run on the test branch.
|
||||||
#
|
#
|
||||||
@@ -61,6 +61,22 @@ def github_config_file_transform(content: str) -> str:
|
|||||||
"push:",
|
"push:",
|
||||||
content,
|
content,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# use the version of cibuildwheel from the current commit, not the latest
|
||||||
|
# release
|
||||||
|
# replace:
|
||||||
|
# """
|
||||||
|
# uses: pypa/cibuildwheel@v3.3.1
|
||||||
|
# """
|
||||||
|
# with:
|
||||||
|
# """
|
||||||
|
# uses: pypa/cibuildwheel@<latest commit hash>
|
||||||
|
# """
|
||||||
|
content = re.sub(
|
||||||
|
r"uses: pypa/cibuildwheel@v.*",
|
||||||
|
f"uses: pypa/cibuildwheel@{git_ref}",
|
||||||
|
content,
|
||||||
|
)
|
||||||
return content
|
return content
|
||||||
|
|
||||||
|
|
||||||
@@ -110,20 +126,22 @@ def ci_service_for_config_file(config_file: Path) -> CIService:
|
|||||||
|
|
||||||
@click.command()
|
@click.command()
|
||||||
@click.argument("config_files", nargs=-1, type=click.Path())
|
@click.argument("config_files", nargs=-1, type=click.Path())
|
||||||
def run_example_ci_configs(config_files=None):
|
def run_example_ci_configs(
|
||||||
|
config_files: list[str],
|
||||||
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Test the example configs. If no files are specified, will test
|
Test the example configs. If no files are specified, will test
|
||||||
examples/*-minimal.yml
|
examples/*-minimal.yml
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if len(config_files) == 0:
|
if len(config_files) == 0:
|
||||||
config_files = Path("examples").glob("*-minimal.yml")
|
config_file_paths = list(Path("examples").glob("*-minimal.yml"))
|
||||||
else:
|
else:
|
||||||
config_files = [Path(f) for f in config_files]
|
config_file_paths = [Path(f) for f in config_files]
|
||||||
|
|
||||||
# check each CI service has at most 1 config file
|
# check each CI service has at most 1 config file
|
||||||
configs_by_service = set()
|
configs_by_service = set()
|
||||||
for config_file in config_files:
|
for config_file in config_file_paths:
|
||||||
service = ci_service_for_config_file(config_file)
|
service = ci_service_for_config_file(config_file)
|
||||||
if service.name in configs_by_service:
|
if service.name in configs_by_service:
|
||||||
msg = "You cannot specify more than one config per CI service"
|
msg = "You cannot specify more than one config per CI service"
|
||||||
@@ -137,6 +155,9 @@ def run_example_ci_configs(config_files=None):
|
|||||||
previous_branch = shell(
|
previous_branch = shell(
|
||||||
"git rev-parse --abbrev-ref HEAD", check=True, capture_output=True, encoding="utf8"
|
"git rev-parse --abbrev-ref HEAD", check=True, capture_output=True, encoding="utf8"
|
||||||
).stdout.strip()
|
).stdout.strip()
|
||||||
|
git_ref = shell(
|
||||||
|
"git rev-parse HEAD", check=True, capture_output=True, encoding="utf8"
|
||||||
|
).stdout.strip()
|
||||||
|
|
||||||
timestamp = time.strftime("%Y-%m-%dT%H-%M-%S", time.gmtime())
|
timestamp = time.strftime("%Y-%m-%dT%H-%M-%S", time.gmtime())
|
||||||
branch_name = f"example-config-test---{previous_branch}-{timestamp}"
|
branch_name = f"example-config-test---{previous_branch}-{timestamp}"
|
||||||
@@ -147,14 +168,14 @@ def run_example_ci_configs(config_files=None):
|
|||||||
example_project = Path("example_root")
|
example_project = Path("example_root")
|
||||||
generate_basic_project(example_project)
|
generate_basic_project(example_project)
|
||||||
|
|
||||||
for config_file in config_files:
|
for config_file in config_file_paths:
|
||||||
service = ci_service_for_config_file(config_file)
|
service = ci_service_for_config_file(config_file)
|
||||||
dst_config_file = example_project / service.dst_config_path
|
dst_config_file = example_project / service.dst_config_path
|
||||||
|
|
||||||
dst_config_file.parent.mkdir(parents=True, exist_ok=True)
|
dst_config_file.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
contents = config_file.read_text(encoding="utf8")
|
contents = config_file.read_text(encoding="utf8")
|
||||||
contents = service.config_file_transform(contents)
|
contents = service.config_file_transform(contents, git_ref)
|
||||||
dst_config_file.write_text(contents, encoding="utf8")
|
dst_config_file.write_text(contents, encoding="utf8")
|
||||||
|
|
||||||
subprocess.run(["git", "add", example_project], check=True)
|
subprocess.run(["git", "add", example_project], check=True)
|
||||||
@@ -178,14 +199,14 @@ def run_example_ci_configs(config_files=None):
|
|||||||
print("> ")
|
print("> ")
|
||||||
print("> | Service | Config | Status |")
|
print("> | Service | Config | Status |")
|
||||||
print("> |---|---|---|")
|
print("> |---|---|---|")
|
||||||
for config_file in config_files:
|
for config_file in config_file_paths:
|
||||||
service = ci_service_for_config_file(config_file)
|
service = ci_service_for_config_file(config_file)
|
||||||
badge = service.badge_md.format(
|
badge = service.badge_md.format(
|
||||||
branch=branch_name, branch_escaped=quote(branch_name, safe="")
|
branch=branch_name, branch_escaped=quote(branch_name, safe="")
|
||||||
)
|
)
|
||||||
print(f"> | {service.name} | `{config_file}` | {badge} |")
|
print(f"> | {service.name} | `{config_file}` | {badge} |")
|
||||||
print("> ")
|
print("> ")
|
||||||
print("> Generated by `bin/run_example_ci_config.py`")
|
print(f"> Generated by `{' '.join(sys.argv)}`")
|
||||||
print()
|
print()
|
||||||
print("---")
|
print("---")
|
||||||
finally:
|
finally:
|
||||||
|
|||||||
Reference in New Issue
Block a user