aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-08-30 16:24:31 +0900
committernsfisis <nsfisis@gmail.com>2026-08-30 16:41:10 +0900
commitdfd9e72b1786597db643d50c7a55d1fcbadf7802 (patch)
tree0d01d8547e4ddc372931b3a07d2467a0a5847c61
parent2f6689c3c1476c40a7c722491c1fcd5763e7d55f (diff)
downloadphp-shirabe-dfd9e72b1786597db643d50c7a55d1fcbadf7802.tar.gz
php-shirabe-dfd9e72b1786597db643d50c7a55d1fcbadf7802.tar.zst
php-shirabe-dfd9e72b1786597db643d50c7a55d1fcbadf7802.zip
build(nix): add a package output producing the binary
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
-rw-r--r--.gitignore1
-rw-r--r--README.md7
-rw-r--r--crates/shirabe/build.rs38
-rw-r--r--flake.lock18
-rw-r--r--flake.nix102
-rwxr-xr-xscripts/linters/lint2
-rw-r--r--scripts/linters/src/Linters/ComposerPin.php63
7 files changed, 226 insertions, 5 deletions
diff --git a/.gitignore b/.gitignore
index e8ab8dd2..f197ab5b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
/target
+/result
/crates/shirabe/tests/plugin/fixtures/e2e/ext/
/crates/shirabe/tests/plugin/fixtures/e2e-normalize/ext/
/crates/shirabe/tests/plugin/fixtures/e2e-installers/ext/
diff --git a/README.md b/README.md
index ba84d1ba..cfc017a8 100644
--- a/README.md
+++ b/README.md
@@ -27,7 +27,6 @@ Building Shirabe requires:
* Direnv (optional)
Nix flake supported; use `nix develop` to enter the development shell.
-TODO: support `nix build`
## Build
@@ -38,6 +37,12 @@ $ composer install --no-dev --working-dir=composer
$ cargo build --release
```
+### Nix
+
+```
+$ nix build
+```
+
## Test
diff --git a/crates/shirabe/build.rs b/crates/shirabe/build.rs
index e11b1a06..59d11ab8 100644
--- a/crates/shirabe/build.rs
+++ b/crates/shirabe/build.rs
@@ -16,9 +16,30 @@ fn git(repo_root: &std::path::Path, args: &[&str]) -> Option<String> {
Some(String::from_utf8(output.stdout).ok()?.trim().to_string())
}
-fn main() {
- println!("cargo::rerun-if-changed=build.rs");
+fn from_env() -> Option<(String, Option<i64>)> {
+ let release_date = std::env::var("SHIRABE_RELEASE_DATE").ok();
+ let dev_warning_time = std::env::var("SHIRABE_DEV_WARNING_TIME").ok();
+ match (release_date, dev_warning_time) {
+ (Some(release_date), Some(dev_warning_time)) => {
+ let dev_warning_time = if dev_warning_time.is_empty() {
+ None
+ } else {
+ Some(dev_warning_time.parse().expect(
+ "SHIRABE_DEV_WARNING_TIME holds a Unix timestamp, or nothing at all for a \
+ tagged release",
+ ))
+ };
+ Some((release_date, dev_warning_time))
+ }
+ (None, None) => None,
+ _ => panic!(
+ "SHIRABE_RELEASE_DATE and SHIRABE_DEV_WARNING_TIME both describe the commit the build \
+ comes from: set them together, or set neither and build from a git checkout"
+ ),
+ }
+}
+fn from_git() -> (String, Option<i64>) {
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap();
let repo_root = std::path::Path::new(&manifest_dir)
.parent()
@@ -67,6 +88,19 @@ fn main() {
Some(commit_time + 60 * 86400)
};
+ (release_date, dev_warning_time)
+}
+
+fn main() {
+ println!("cargo::rerun-if-changed=build.rs");
+ println!("cargo::rerun-if-env-changed=SHIRABE_RELEASE_DATE");
+ println!("cargo::rerun-if-env-changed=SHIRABE_DEV_WARNING_TIME");
+
+ let (release_date, dev_warning_time) = match from_env() {
+ Some(values) => values,
+ None => from_git(),
+ };
+
let out_dir = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap());
std::fs::write(out_dir.join("release_date.rs"), format!("{release_date:?}")).unwrap();
std::fs::write(
diff --git a/flake.lock b/flake.lock
index 76841d03..c1f4fa97 100644
--- a/flake.lock
+++ b/flake.lock
@@ -1,5 +1,22 @@
{
"nodes": {
+ "composer": {
+ "flake": false,
+ "locked": {
+ "lastModified": 1776166312,
+ "narHash": "sha256-cmz5YaxfykkUlF7Ai0Yu8L6sfNePmx3v24g6131+/RM=",
+ "owner": "composer",
+ "repo": "composer",
+ "rev": "82a2fbd1372a98d7915cfb092acf05207d9b4113",
+ "type": "github"
+ },
+ "original": {
+ "owner": "composer",
+ "ref": "2.9.7",
+ "repo": "composer",
+ "type": "github"
+ }
+ },
"flake-utils": {
"inputs": {
"systems": [
@@ -38,6 +55,7 @@
},
"root": {
"inputs": {
+ "composer": "composer",
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs",
"rust-overlay": "rust-overlay",
diff --git a/flake.nix b/flake.nix
index 5b51672e..5cd80bee 100644
--- a/flake.nix
+++ b/flake.nix
@@ -6,6 +6,11 @@
systems.url = "github:nix-systems/default";
+ composer = {
+ url = "github:composer/composer/2.9.7";
+ flake = false;
+ };
+
flake-utils = {
url = "github:numtide/flake-utils";
inputs.systems.follows = "systems";
@@ -24,7 +29,9 @@
outputs =
{
+ self,
nixpkgs,
+ composer,
flake-utils,
rust-overlay,
treefmt-nix,
@@ -42,15 +49,106 @@
imports = [ ./treefmt.nix ];
programs.rustfmt.package = rustToolchain;
};
+
+ php = pkgs.php85;
+
+ composerVersion =
+ let
+ versionLines = builtins.filter (match: match != null) (
+ map (line: builtins.match " *public const VERSION = '(.*)';" line) (
+ pkgs.lib.splitString "\n" (builtins.readFile "${composer}/src/Composer/Composer.php")
+ )
+ );
+ in
+ builtins.head (builtins.head versionLines);
+
+ composerRuntime = pkgs.stdenvNoCC.mkDerivation {
+ pname = "composer-runtime";
+ version = composerVersion;
+ src = composer;
+ nativeBuildInputs = [
+ php
+ php.packages.composer
+ ];
+ dontPatchShebangs = true;
+ buildPhase = ''
+ runHook preBuild
+ export COMPOSER_HOME="$TMPDIR/composer-home"
+ export COMPOSER_CACHE_DIR="$TMPDIR/composer-cache"
+ export COMPOSER_ROOT_VERSION="${composerVersion}"
+ composer install --no-dev --no-interaction --no-progress
+ runHook postBuild
+ '';
+ installPhase = ''
+ runHook preInstall
+ cp -r . "$out"
+ runHook postInstall
+ '';
+
+ outputHashMode = "recursive";
+ outputHashAlgo = "sha256";
+ outputHash = "sha256-owdTo9YBaQv3a3FnUubBfI0gjca5XEJ84/PukcukPfc=";
+ };
+
+ commitTime = self.lastModified;
+ commitDate = self.lastModifiedDate;
+
+ rustPlatform = pkgs.makeRustPlatform {
+ cargo = rustToolchain;
+ rustc = rustToolchain;
+ };
+
+ shirabe = rustPlatform.buildRustPackage {
+ pname = "shirabe";
+ version = (builtins.fromTOML (builtins.readFile ./Cargo.toml)).workspace.package.version;
+
+ src = pkgs.lib.fileset.toSource {
+ root = ./.;
+ fileset = pkgs.lib.fileset.unions [
+ ./Cargo.toml
+ ./Cargo.lock
+ ./crates
+ ];
+ };
+ cargoLock.lockFile = ./Cargo.lock;
+
+ postPatch = ''
+ cp -r ${composerRuntime} composer
+ chmod -R u+w composer
+ '';
+
+ env = {
+ SHIRABE_RELEASE_DATE =
+ let
+ field = start: builtins.substring start 2 commitDate;
+ in
+ "${builtins.substring 0 4 commitDate}-${field 4}-${field 6} ${field 8}:${field 10}:${field 12}";
+ SHIRABE_DEV_WARNING_TIME = toString (commitTime + 60 * 86400);
+ };
+
+ doCheck = false;
+
+ meta = {
+ description = "Rust port of Composer, the dependency manager for PHP";
+ homepage = "https://github.com/nsfisis/php-shirabe";
+ license = pkgs.lib.licenses.mit;
+ mainProgram = "shirabe";
+ };
+ };
in
{
formatter = treefmt.config.build.wrapper;
+ packages = {
+ inherit shirabe;
+ default = shirabe;
+ };
+
devShells.default = pkgs.mkShell {
packages = [
rustToolchain
- pkgs.php85
- pkgs.php85Packages.composer
+ php
+ php.packages.composer
pkgs.just
# The following softwares are used for testing:
diff --git a/scripts/linters/lint b/scripts/linters/lint
index 150e1f1a..7e7c5fe6 100755
--- a/scripts/linters/lint
+++ b/scripts/linters/lint
@@ -6,6 +6,7 @@ declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use Shirabe\Lint\Linters\CargoWorkspaceDependencies;
+use Shirabe\Lint\Linters\ComposerPin;
use Shirabe\Lint\Linters\ContiguousUseBlock;
use Shirabe\Lint\Linters\NoBannedUse;
use Shirabe\Lint\Linters\NoDecorativeSectionComment;
@@ -24,6 +25,7 @@ $rootDir = dirname(__DIR__, 2);
$runner = new Runner($rootDir, [
[new CargoWorkspaceDependencies(), []],
+ [new ComposerPin(), []],
[new ContiguousUseBlock(), []],
[new NoBannedUse(), []],
[new NoDecorativeSectionComment(), [
diff --git a/scripts/linters/src/Linters/ComposerPin.php b/scripts/linters/src/Linters/ComposerPin.php
new file mode 100644
index 00000000..d1db55fe
--- /dev/null
+++ b/scripts/linters/src/Linters/ComposerPin.php
@@ -0,0 +1,63 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Shirabe\Lint\Linters;
+
+use Shirabe\Lint\Linter;
+
+/**
+ * The Composer sources are pinned twice: as the `composer` submodule, which a `cargo` build reads,
+ * and as the `composer` flake input, which a `nix build` reads in its place because a flake source
+ * carries no submodule. Both have to name the same commit, or the two builds embed different
+ * Composer runtimes.
+ */
+final class ComposerPin implements Linter
+{
+ public function name(): string
+ {
+ return 'composer_pin';
+ }
+
+ public function failureIntro(): string
+ {
+ return "The `composer` submodule and the `composer` flake input name different commits.\n"
+ . 'Point the input at the submodule commit and run `nix flake lock`:';
+ }
+
+ public function check(string $rootDir, array $excludes): array
+ {
+ $submoduleRev = $this->submoduleRev($rootDir);
+ $inputRev = $this->inputRev($rootDir);
+
+ if ($submoduleRev === $inputRev) {
+ return [];
+ }
+
+ return [
+ "submodule: {$submoduleRev}",
+ "flake input: {$inputRev}",
+ ];
+ }
+
+ private function submoduleRev(string $rootDir): string
+ {
+ $command = sprintf('git -C %s ls-tree HEAD composer', escapeshellarg($rootDir));
+ exec($command, $output, $status);
+ if ($status !== 0 || $output === []) {
+ return '(unreadable: git ls-tree HEAD composer failed)';
+ }
+
+ // `<mode> commit <rev>\tcomposer`
+ $fields = preg_split('/\s+/', $output[0]);
+
+ return $fields[2] ?? '(unreadable: unexpected git ls-tree output)';
+ }
+
+ private function inputRev(string $rootDir): string
+ {
+ $lock = json_decode((string) file_get_contents("{$rootDir}/flake.lock"), true);
+
+ return $lock['nodes']['composer']['locked']['rev'] ?? '(missing from flake.lock)';
+ }
+}