-
Notifications
You must be signed in to change notification settings - Fork 0
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
Introduce experimental fetchComposerDepsImpure
#5
Open
jtojnar
wants to merge
1
commit into
main
Choose a base branch
from
impure-build
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,13 @@ | ||
#!/usr/bin/env bash | ||
set -x -o errexit | ||
|
||
nix develop --no-write-lock-file ./tests#python -c black --check --diff src/composer-create-repository.py | ||
nix develop --no-write-lock-file ./tests#python -c mypy --strict src/composer-create-repository.py | ||
|
||
nix build -L --no-write-lock-file --extra-experimental-features impure-derivations ./tests#composer-impure | ||
nix build -L --no-write-lock-file --extra-experimental-features impure-derivations ./tests#grav-impure | ||
nix build -L --no-write-lock-file --extra-experimental-features impure-derivations ./tests#non-head-rev-impure | ||
|
||
nix build -L --no-write-lock-file ./tests#composer | ||
nix build -L --no-write-lock-file ./tests#grav | ||
nix build -L --no-write-lock-file ./tests#non-head-rev |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
#!/usr/bin/env python3 | ||
from pathlib import Path | ||
from typing import cast, NotRequired, TypedDict | ||
import argparse | ||
import json | ||
import shutil | ||
import subprocess | ||
|
||
Source = TypedDict( | ||
"Source", | ||
{ | ||
"type": str, | ||
"url": str, | ||
"reference": str, | ||
}, | ||
) | ||
|
||
|
||
class Package(TypedDict): | ||
name: str | ||
version: str | ||
source: NotRequired[Source] | ||
dist: Source | ||
|
||
|
||
def clone_git_repo(url: str, rev: str, clone_target_path: Path) -> None: | ||
subprocess.check_call( | ||
["git", "init"], | ||
cwd=clone_target_path, | ||
) | ||
subprocess.check_call( | ||
["git", "fetch", url, rev, "--depth", "1"], | ||
cwd=clone_target_path, | ||
) | ||
subprocess.check_call( | ||
["git", "reset", "--hard", "FETCH_HEAD"], | ||
cwd=clone_target_path, | ||
) | ||
|
||
|
||
def fetch_composer_package(package: Package, clone_target_path: Path) -> None: | ||
assert ( | ||
"source" in package and package["source"]["type"] == "git" | ||
), f"Package “{package['name']}” does not have source of type “git”." | ||
|
||
clone_git_repo( | ||
url=package["source"]["url"], | ||
rev=package["source"]["reference"], | ||
clone_target_path=clone_target_path, | ||
) | ||
|
||
# Clean up git directory to ensure reproducible output | ||
shutil.rmtree(clone_target_path / ".git") | ||
|
||
|
||
def make_package( | ||
package: Package, | ||
clone_target_path: Path, | ||
) -> tuple[str, dict[str, Package]]: | ||
assert ( | ||
package["source"]["reference"] == package["dist"]["reference"] | ||
), f"Package “{package['name']}” has a mismatch between “reference” keys of “dist” and “source” keys." | ||
|
||
# While Composer repositories only really require `name`, `version` and `source`/`dist` fields, | ||
# we will use the original contents of the package’s entry from `composer.lock`, modifying just the sources. | ||
# Package entries in Composer repositories correspond to `composer.json` files [1] | ||
# and Composer appears to use them when regenerating the lockfile. | ||
# If we just used the minimal info, stuff like `autoloading` or `bin` programs would be broken. | ||
# | ||
# We cannot use `source` since Composer does not support path sources: | ||
# "PathDownloader" is a dist type downloader and can not be used to download source | ||
# | ||
# [1]: https://getcomposer.org/doc/05-repositories.md#packages> | ||
|
||
# Copy the Package so that we do not mutate the original. | ||
package = cast(Package, dict(package)) | ||
package.pop("source", None) | ||
package["dist"] = { | ||
"type": "path", | ||
"url": str(clone_target_path / package["name"] / package["version"]), | ||
"reference": package["dist"]["reference"], | ||
} | ||
|
||
return ( | ||
package["name"], | ||
{ | ||
package["version"]: package, | ||
}, | ||
) | ||
|
||
|
||
def main( | ||
lockfile_path: Path, | ||
output_path: Path, | ||
) -> None: | ||
# We are generating a repository of type Composer | ||
# https://getcomposer.org/doc/05-repositories.md#composer | ||
with open(lockfile_path) as lockfile: | ||
lock = json.load(lockfile) | ||
repo_path = output_path / "repo" | ||
|
||
# We always need to fetch dev dependencies so that `composer update --lock` can update the config. | ||
packages_to_install = lock["packages"] + lock["packages-dev"] | ||
|
||
for package in packages_to_install: | ||
clone_target_path = repo_path / package["name"] / package["version"] | ||
clone_target_path.mkdir(parents=True) | ||
fetch_composer_package(package, clone_target_path) | ||
|
||
repo_manifest = { | ||
"packages": { | ||
package_name: metadata | ||
for package_name, metadata in [ | ||
make_package(package, repo_path) for package in packages_to_install | ||
] | ||
} | ||
} | ||
with open(output_path / "packages.json", "w") as repo_manifest_file: | ||
json.dump( | ||
repo_manifest, | ||
repo_manifest_file, | ||
indent=4, | ||
) | ||
|
||
|
||
if __name__ == "__main__": | ||
parser = argparse.ArgumentParser( | ||
description="Generate composer repository for offline fetching" | ||
) | ||
parser.add_argument( | ||
"lockfile_path", | ||
help="Path to a composer lockfile", | ||
) | ||
parser.add_argument( | ||
"output_path", | ||
help="Output path to store the repository in", | ||
) | ||
|
||
args = parser.parse_args() | ||
|
||
main( | ||
lockfile_path=Path(args.lockfile_path), | ||
output_path=Path(args.output_path), | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
{ | ||
runCommand, | ||
lib, | ||
python311, | ||
git, | ||
cacert, | ||
}: | ||
|
||
{ | ||
src ? null, | ||
lockFile ? null, | ||
}: | ||
|
||
assert lib.assertMsg ((src == null) != (lockFile == null)) "Either “src” or “lockFile” attribute needs to be provided."; | ||
|
||
let | ||
lockPath = | ||
if lockFile != null then | ||
# Interpolated to create a store object. | ||
"${lockFile}" | ||
else | ||
"${src}/composer.lock"; | ||
in | ||
# We are generating a repository of type Composer | ||
# https://getcomposer.org/doc/05-repositories.md#composer | ||
runCommand "repo" { | ||
__impure = true; | ||
|
||
nativeBuildInputs = [ | ||
python311 | ||
git | ||
cacert | ||
]; | ||
} '' | ||
python3 "${./composer-create-repository.py}" ${lib.escapeShellArg lockPath} "$out" | ||
'' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we should just re-use
c4.fetchComposerDeps
and change the implementation when passed__impure
argument. We could even passhash
to get FOD.