diff --git a/nur_bin/compdep.php b/nur_bin/compdep.php new file mode 100755 index 0000000..6653add --- /dev/null +++ b/nur_bin/compdep.php @@ -0,0 +1,5 @@ +#!/usr/bin/php + "Lister les dépendances d'un projet PHP", + + ["-B", "--projects-dir", "args" => "dir", + "help" => "Spécifier le répertoire de base des projets", + ], + ["-w", "--rewrite", + "help" => "modifier le fichier composer.json au lieu d'afficher le nouveau contenu", + ], + ["--force-rewrite", + "help" => "en cas de modification, forcer la ré-écriture du fichier composer.json même s'il est inchangé", + ], + ["-s", "--set-pkey", "args" => 1, "argsdesc" => "PKEY=VALUE", + "help" => "créer ou modifier le chemin de clé avec la valeur spécifiée", + ], + ["-d", "--del-pkey", "args" => 1, "argsdesc" => "PKEY", + "help" => "supprimer le chemin de clé spécifié", + ], + ["-r", "--filter-dep", "args" => 1, "argsdesc" => "DEP", + "help" => "sélectionner tous les projets qui ont une dépendance sur la librairie spécifiée", + ], + ["-u", "--update-dep", "args" => "value", "argsdesc" => "VERSION", + "help" => "mettre à jour la dépendance avec la nouvelle cible/version. +requière l'utilisation de --filter-dep", + ], + ["--sn", "--show-names", "name" => "show", "value" => self::SHOW_NAMES, + "help" => "afficher les noms des projets", + ], + ["--sp", "--show-deps", "name" => "show", "value" => self::SHOW_DEPS, + "help" => "afficher les branches", + ], + ["--sb", "--show-branches", + "help" => "avec --show-deps, afficher aussi les branches", + ], + ["--sf", "--show-filtered", + "help" => "avec --show-deps, n'afficher que la dépendances sélectionnée +implique --show-branches", + ], + ["--sd", "--show-dirs", "name" => "show", "value" => self::SHOW_DIRS, + "help" => "afficher les répertoires des projets", + ], + ["--sc", "--show-composer", "name" => "show", "value" => self::SHOW_COMPOSER, + "help" => "afficher les chemins des fichiers composer.json de chaque projet", + ], + ]; + + private $projectsDir; + private $rewrite = false, $forceRewrite = false; + private $setPkey, $delPkey; + private $filterDep, $updateDep; + private $show = self::SHOW_DEPS, $showBranches, $showFiltered; + private $args; + + function main() { + $projectDirs = $this->args; + $rewrite = $this->rewrite; + $forceRewrite = $this->forceRewrite; + $setPkey = $this->setPkey; + $delPkey = $this->delPkey; + $filterDep = $this->filterDep; + $updateDep = $this->updateDep; + $projectsDir = $this->projectsDir; + $show = $this->show; + $showBranches = $this->showBranches; + $showFiltered = $this->showFiltered; + + if ($updateDep && !$filterDep) { + self::die("--update-dep requière --filter-dep"); + } + + if (!$projectDirs) { + if ($projectsDir !== null) { + $projectDirs = shutils::ls_pdirs($projectsDir); + } else { + if (file_exists("composer.json")) { + $projectDirs = ["."]; + } else { + $projectsDir = "."; + $projectDirs = shutils::ls_pdirs($projectsDir); + } + } + } + $projects = ComposerWalker::build_projects($projectDirs, $projectsDir); + $oprojects = ComposerWalker::order_deps(null, $projects); + + foreach ($oprojects as $name) { + $project = A::get($projects, $name); + if ($project === null) continue; + $requires = ComposerWalker::order_deps($project["requires"], $projects); + $branches = $project["branches"]; + if ($filterDep && !in_array($filterDep, $requires)) { + continue; + } + + if ($setPkey || $delPkey || $updateDep) { + /** @var ComposerFile $composer */ + $composer = $project["composer"]; + msg::step($composer->getComposerFile()); + if ($setPkey) { + [$pkey, $value] = self::split_pkey_value($setPkey); + $composer->setPkey($pkey, $value, $rewrite, $forceRewrite); + } elseif ($delPkey) { + [$pkey, $value] = self::split_pkey_value($delPkey); + $composer->delPkey($pkey, $rewrite, $forceRewrite); + } elseif ($updateDep) { + $composer->updateDep($filterDep, $updateDep, $rewrite, $forceRewrite); + } + } else { + switch ($show) { + case self::SHOW_NAMES: + echo $name; + break; + case self::SHOW_DEPS: + echo $name; $pos = strlen($name); + $sep = ": "; + foreach ($requires as $require) { + if ($showFiltered && $require !== $filterDep) continue; + $text = $require; + if ($showBranches || $showFiltered) { + $branch = A::get($branches, $require); + if ($branch !== null) $text .= "[$branch]"; + } + $size = strlen("$sep$text"); + if ($pos + $size > 80) { + $sep = "\n".str_repeat(" ", 4); $pos = 4; + $size = strlen($text); + } + echo "$sep$text"; $pos += $size; + $sep = ", "; + } + break; + case self::SHOW_DIRS: + echo $project["dir"]; + break; + case self::SHOW_COMPOSER: + echo "$project[dir]/composer.json"; + break; + } + echo "\n"; + } + } + } +} diff --git a/nur_src/tools/compctl/ComposerFile.php b/nur_src/tools/compctl/ComposerFile.php index bb6cc89..d18c575 100644 --- a/nur_src/tools/compctl/ComposerFile.php +++ b/nur_src/tools/compctl/ComposerFile.php @@ -15,7 +15,7 @@ class ComposerFile { function __construct(string $composerFile, ?string $projdir=null, bool $ensureExists=true) { if ($ensureExists && !file_exists($composerFile)) { if ($projdir === null) $projdir = dirname($composerFile); - $message = path::ppath($projdir).": aucune composeruration n'a été trouvée"; + $message = path::ppath($projdir).": aucune configuration n'a été trouvée"; throw new ValueException($message); } $this->composerFile = $composerFile; @@ -24,6 +24,10 @@ class ComposerFile { /** @var string */ protected $composerFile; + function getComposerFile(): string { + return $this->composerFile; + } + /** @var array */ protected $data; @@ -196,15 +200,36 @@ class ComposerFile { return [$data != $orig, $data]; } - function sync(ConfigFile $config, string $branch, bool $inplace=false): bool { - [$modified, $json] = $this->computeComposer($config, $branch); - $contents = json::with($json, json::INDENT_TABS); - if ($inplace) { - if ($modified) file::with($this->composerFile)->putContents($contents); + protected function _save(array $data, bool $modified, bool $rewrite, bool $forceRewrite): bool { + $contents = json::with($data, json::INDENT_TABS); + if ($rewrite) { + $contents .= "\n"; + if ($modified || $forceRewrite) file::with($this->composerFile)->putContents($contents); else msg::info("Aucune modifications effectuée"); } else { - if ($contents !== null) echo "$contents\n"; + if ($contents) echo "$contents\n"; } return $modified; } + + function sync(ConfigFile $config, string $branch, bool $rewrite=false, bool $forceRewrite=false): bool { + [$modified, $data] = $this->computeComposer($config, $branch); + return $this->_save($data, $modified, $rewrite, $forceRewrite); + } + + function setPkey(string $pkey, $value, bool $rewrite=false, bool $forceRewrite=false): bool { + $data = $orig = $this->load(); + A::pset($data, $pkey, $value); + return $this->_save($data, $data != $orig, $rewrite, $forceRewrite); + } + + function delPkey(string $pkey, bool $rewrite=false, bool $forceRewrite=false): bool { + $data = $orig = $this->load(); + A::pdel($data, $pkey); + return $this->_save($data, $data != $orig, $rewrite, $forceRewrite); + } + + function updateDep(string $dep, string $version, bool $rewrite=false, bool $forceRewrite=false): bool { + return $this->setPkey("require.$dep", $version, $rewrite, $forceRewrite); + } } diff --git a/nur_src/tools/compctl/ComposerWalker.php b/nur_src/tools/compctl/ComposerWalker.php new file mode 100644 index 0000000..7921922 --- /dev/null +++ b/nur_src/tools/compctl/ComposerWalker.php @@ -0,0 +1,115 @@ +getv("name"); + if (array_key_exists($pname, $projects) && $projects[$pname] !== null) { + # projet déjà analysé + return $projects[$pname]; + } + + $project = [ + "composer" => $composer, + "name" => $pname, + "requires" => [], + "branches" => [], + "dir" => realpath($dir), + ]; + $requires = $composer->geta("require"); + if ($requires !== null) { + foreach ($requires as $dep => $version) { + # ignorer les projets qui n'ont pas le préfixe requis + $include_project = false; + foreach (ConfigFile::PREFIXES as $prefix) { + if (substr($dep, 0, strlen($prefix)) === $prefix) { + $include_project = true; + break; + } + } + if (!$include_project) continue; + # sinon, ajouter le projet aux dépendances + $project["requires"][] = $dep; + if (!array_key_exists($dep, $projects)) { + $projects[$dep] = null; + $new_projects[$dep] = true; + } + if (substr($version, 0, 4) === "dev-") { + $project["branches"][$dep] = substr($version, 4); + } else { + $project["branches"][$dep] = $version; + } + } + } + $projects[$pname] = $project; + return $project; + } + + static function build_projects(array $project_dirs, ?string &$projects_dir): array { + $projects = []; + foreach ($project_dirs as $project_dir) { + if ($projects_dir === null) $projects_dir = dirname($project_dir); + # analyser le projet initial + $new_projects = []; + if (self::parse_project($project_dir, $projects, $new_projects) === null) { + continue; + } + # puis analyser récursivement tous les projets dépendants + while (true) { + $have_new = false; + foreach ($new_projects as $name => &$new) { + if ($new) { + $new = false; + $have_new = true; + $project_dir = self::get_project_dir($name, $projects_dir); + self::parse_project($project_dir, $projects, $new_projects); + } + }; unset($new); + if (!$have_new) break; + } + } + return $projects; + } + + static function order_deps(?array $deps, array $projects): array { + if ($deps === null) $deps = array_keys($projects); + $pi = 0; + $max = count($deps); + while ($pi < $max) { + $modified = false; + $project = $projects[$deps[$pi]]; + if ($project !== null) { + # dépendance dont on a trouvé la correspondance sur disque + foreach ($project["requires"] as $dep) { + $di = array_search($dep, $deps); + if ($di > $pi) { + # si la dépendance se situe après le projet, la placer avant + $deps = array_splice($deps, $di, 1); + $deps = array_splice($deps, $pi, 0, [$dep]); + $modified = true; + } + } + } + if (!$modified) $pi++; + } + return $deps; + } +} diff --git a/nur_src/tools/compctl/ConfigFile.php b/nur_src/tools/compctl/ConfigFile.php index 1e47aae..a822ff2 100644 --- a/nur_src/tools/compctl/ConfigFile.php +++ b/nur_src/tools/compctl/ConfigFile.php @@ -10,6 +10,9 @@ use nur\str; use nur\yaml; class ConfigFile { + # préfixes de projets dont on tient compte + const PREFIXES = ["ur/", "nur/", "lib/", "nulib/"]; + const DEFAULT_DEP = "dev-master"; const DEFAULT_DEPS = [ "wip" => "dev-wip",