diff --git a/README.md b/README.md index 5d2c3a0..36db766 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ ``` Usage: jenkinsPlugins2nix [-r|--dependency-resolution [as-given|latest|jenkins[:VERSION]]] + [--no-deps] [--skip-optional] (-p|--plugin PLUGIN_NAME{:PLUGIN_VERSION}) Generate nix expressions for requested Jenkins plugins. @@ -12,8 +13,11 @@ Available options: `jenkins:` to resolve against a specific Jenkins version (for example: `-r jenkins:2.401`). -p,--plugin PLUGIN_NAME{:PLUGIN_VERSION} - Plugins we should generate nix for. Latest version is - used if not specified. + Plugins we should generate nix for. Latest version is + used if not specified. + --no-deps Do not download or include transitive dependencies; + only generate nix for explicitly requested --plugin + entries. --skip-optional Skip optional dependencies when downloading plugins -h,--help Show this help text @@ -63,3 +67,10 @@ By default the tool downloads all dependencies (including optional ones) to avoid backward-compatibility problems. Use `--skip-optional` to exclude optional dependencies from downloads when you prefer a minimal set of dependencies. + +### No dependency downloads + +If you already have a fully resolved/pinned plugins list (for example produced by +`jenkins-plugin-cli --list`) and only want the nix conversion, use `--no-deps`. +This prevents `jenkinsPlugins2nix` from downloading any transitive dependencies +and will only produce output for the plugins explicitly provided via `--plugin`. diff --git a/app/Main.hs b/app/Main.hs index 37a6e0f..782d39c 100644 --- a/app/Main.hs +++ b/app/Main.hs @@ -44,12 +44,15 @@ parseConfig = Config <> Opt.showDefaultWith (resolutions Bimap.!) <> Opt.metavar (printf "[%s]" . concat . intersperse "|" $ Bimap.keysR resolutions) <> Opt.value Latest ) + <*> Opt.switch + ( Opt.long "no-deps" + <> Opt.help "Do not download or include transitive dependencies; only generate nix for explicitly requested --plugin entries." ) <*> Opt.some (Opt.option requestedPluginReader - ( Opt.metavar "PLUGIN_NAME{:PLUGIN_VERSION}" - <> Opt.long "plugin" - <> Opt.short 'p' - <> Opt.help "Plugins we should generate nix for. Latest version is used if not specified." ) - ) + ( Opt.metavar "PLUGIN_NAME{:PLUGIN_VERSION}" + <> Opt.long "plugin" + <> Opt.short 'p' + <> Opt.help "Plugins we should generate nix for. Latest version is used if not specified." ) + ) <*> Opt.flag Optional Mandatory ( Opt.long "skip-optional" <> Opt.help "skip optional dependencies" ) @@ -58,14 +61,27 @@ parseConfig = Config resolutions = Bimap.fromList [(AsGiven, "as-given"), (Latest, "latest"), (JenkinsVersion(""), "jenkins")] resolutionReader :: Opt.ReadM ResolutionStrategy - resolutionReader = let - strat = \s -> (Text.unpack (head (Text.splitOn ":" (Text.pack s)))) - version = \s -> (Text.unpack(last (Text.splitOn ":" (Text.pack s)))) - in Opt.eitherReader $ \s -> case Bimap.lookupR (strat s) resolutions of - Nothing -> Left $ "Invalid dependency resolution, needs to be one of " - <> show (Bimap.keysR resolutions) - Just (JenkinsVersion("")) -> Right (JenkinsVersion(version s)) - Just v -> Right v + resolutionReader = + let + splitOnce :: String -> (String, Maybe String) + splitOnce s = + let (k, rest) = Text.breakOn ":" (Text.pack s) + in if Text.null rest + then (Text.unpack k, Nothing) + else (Text.unpack k, Just (Text.unpack (Text.drop 1 rest))) + + in Opt.eitherReader $ \s -> + let (k, mv) = splitOnce s + in case Bimap.lookupR k resolutions of + Nothing -> Left $ + "Invalid dependency resolution, needs to be one of " + <> show (Bimap.keysR resolutions) + Just (JenkinsVersion "") -> + case mv of + Nothing -> Left "Invalid dependency resolution: jenkins strategy needs a version (jenkins:)" + Just "" -> Left "Invalid dependency resolution: jenkins strategy needs a version (jenkins:)" + Just v -> Right (JenkinsVersion v) + Just v -> Right v requestedPluginReader :: Opt.ReadM RequestedPlugin requestedPluginReader = Opt.maybeReader $ \p -> Just $! case break (== ':') p of diff --git a/flake.lock b/flake.lock index 21de1be..0d1fdfc 100644 --- a/flake.lock +++ b/flake.lock @@ -1,13 +1,12 @@ { "nodes": { "flake-compat": { - "flake": false, "locked": { - "lastModified": 1673956053, - "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=", + "lastModified": 1767039857, + "narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=", "owner": "edolstra", "repo": "flake-compat", - "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9", + "rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab", "type": "github" }, "original": { @@ -17,12 +16,15 @@ } }, "flake-utils": { + "inputs": { + "systems": "systems" + }, "locked": { - "lastModified": 1667395993, - "narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=", + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", "owner": "numtide", "repo": "flake-utils", - "rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", "type": "github" }, "original": { @@ -33,16 +35,16 @@ }, "nixpkgs": { "locked": { - "lastModified": 1685566663, - "narHash": "sha256-btHN1czJ6rzteeCuE/PNrdssqYD2nIA4w48miQAFloM=", + "lastModified": 1771008912, + "narHash": "sha256-gf2AmWVTs8lEq7z/3ZAsgnZDhWIckkb+ZnAo5RzSxJg=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "4ecab3273592f27479a583fb6d975d4aba3486fe", + "rev": "a82ccc39b39b621151d6732718e3e250109076fa", "type": "github" }, "original": { "owner": "NixOS", - "ref": "23.05", + "ref": "nixos-unstable", "repo": "nixpkgs", "type": "github" } @@ -53,6 +55,21 @@ "flake-utils": "flake-utils", "nixpkgs": "nixpkgs" } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } } }, "root": "root", diff --git a/flake.nix b/flake.nix index de6213a..e120da6 100644 --- a/flake.nix +++ b/flake.nix @@ -2,11 +2,8 @@ description = "jenkinsPlugins2nix"; inputs = { flake-utils.url = "github:numtide/flake-utils"; - nixpkgs.url = "github:NixOS/nixpkgs/23.05"; - flake-compat = { - url = "github:edolstra/flake-compat"; - flake = false; - }; + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + flake-compat.url = "github:edolstra/flake-compat"; }; outputs = { self, flake-compat, flake-utils, nixpkgs }: flake-utils.lib.eachDefaultSystem (system: diff --git a/jenkinsPlugins2nix.cabal b/jenkinsPlugins2nix.cabal index eb7c451..26308b1 100644 --- a/jenkinsPlugins2nix.cabal +++ b/jenkinsPlugins2nix.cabal @@ -20,16 +20,16 @@ library Nix.JenkinsPlugins2Nix.Parser Nix.JenkinsPlugins2Nix.Types build-depends: base >=4.7 && < 5 - , attoparsec >=0.13 && <0.15 - , bytestring >=0.10 && <0.12 - , containers >=0.6 && <0.7 - , cryptonite >=0.24 && <0.31 - , hnix >=0.6 && <0.18 - , http-conduit >=2.3 && <2.4 - , mtl >=2.2 && <2.4 - , prettyprinter >=1.2 && <1.8 - , text - , zip-archive >=0.4 && <0.5 + , attoparsec >=0.13 && <0.15 + , bytestring >=0.10 && <0.13 + , containers >=0.6 && <0.8 + , cryptonite >=0.24 && <0.31 + , hnix >=0.6 && <0.18 + , http-conduit >=2.3 && <2.4 + , mtl >=2.2 && <2.4 + , prettyprinter >=1.2 && <1.8 + , text + , zip-archive >=0.4 && <0.5 default-language: Haskell2010 ghc-options: -Wall @@ -38,11 +38,11 @@ executable jenkinsPlugins2nix main-is: Main.hs ghc-options: -threaded -rtsopts -with-rtsopts=-N -Wall build-depends: base - , bimap >=0.3 && <0.6 - , jenkinsPlugins2nix - , optparse-applicative >=0.14 && <0.18 - , prettyprinter-ansi-terminal >=1.1 && <1.2 - , text + , bimap >=0.3 && <0.6 + , jenkinsPlugins2nix + , optparse-applicative >=0.14 && <0.19 + , prettyprinter-ansi-terminal >=1.1 && <1.2 + , text default-language: Haskell2010 test-suite jenkinsPlugins2nix-test diff --git a/src/Nix/JenkinsPlugins2Nix.hs b/src/Nix/JenkinsPlugins2Nix.hs index 645ac4a..a18e876 100644 --- a/src/Nix/JenkinsPlugins2Nix.hs +++ b/src/Nix/JenkinsPlugins2Nix.hs @@ -69,13 +69,14 @@ downloadPlugin p = do -- | Download the given plugin as well as recursively download its dependencies. downloadPluginsRecursive - :: ResolutionStrategy -- ^ Decide what version of dependencies to pick. + :: Bool -- ^ If 'True', do not recurse into dependencies. + -> ResolutionStrategy -- ^ Decide what version of dependencies to pick. -> PluginResolution -- ^ Wheter to include or skip optional dependencies -> Map Text RequestedPlugin -- ^ Plugins user requested. -> Map Text Plugin -- ^ Already downloaded plugins. -> RequestedPlugin -- ^ Plugin we're going to download. -> MTL.ExceptT String IO (Map Text Plugin) -downloadPluginsRecursive strategy presolution uPs m p = if Map.member (requested_name p) m +downloadPluginsRecursive noDeps strategy presolution uPs m p = if Map.member (requested_name p) m then return m else do -- Adjust the requested plugin based on whether it was @@ -98,22 +99,26 @@ downloadPluginsRecursive strategy presolution uPs m p = if Map.member (requested -- based on versions listed in manifest dependencies. Just userPlugin -> userPlugin plugin <- MTL.ExceptT $ downloadPlugin adjustedPlugin - foldM (\m' p' -> downloadPluginsRecursive strategy presolution uPs m' $ - RequestedPlugin { requested_name = plugin_dependency_name p' - , requested_version = Just $! plugin_dependency_version p' - }) - (Map.insert (requested_name p) plugin m) - (Set.filter (\dep -> plugin_dependency_resolution dep <= presolution) - (plugin_dependencies $ manifest plugin)) + let m' = Map.insert (requested_name p) plugin m + if noDeps + then return m' + else + foldM (\m'' p' -> downloadPluginsRecursive noDeps strategy presolution uPs m'' $ + RequestedPlugin { requested_name = plugin_dependency_name p' + , requested_version = Just $! plugin_dependency_version p' + }) + m' + (Set.filter (\dep -> plugin_dependency_resolution dep <= presolution) + (plugin_dependencies $ manifest plugin)) -- | Pretty-print nix expression for all the given plugins and their -- dependencies that the user asked for. mkExprsFor :: Config -> IO (Either String (Doc ann)) -mkExprsFor (Config { resolution_strategy = st, requested_plugins = ps, plugin_resolution = pr }) = do +mkExprsFor (Config { resolution_strategy = st, no_deps = noDeps, requested_plugins = ps, plugin_resolution = pr }) = do eplugins <- MTL.runExceptT $ do let userPlugins = Map.fromList $ map (requested_name &&& id) ps - plugins <- foldM (downloadPluginsRecursive st pr userPlugins) Map.empty ps + plugins <- foldM (downloadPluginsRecursive noDeps st pr userPlugins) Map.empty ps return $ Map.elems plugins return $! case eplugins of Left err -> Left err diff --git a/src/Nix/JenkinsPlugins2Nix/Parser.hs b/src/Nix/JenkinsPlugins2Nix/Parser.hs index 791151b..e5113c3 100644 --- a/src/Nix/JenkinsPlugins2Nix/Parser.hs +++ b/src/Nix/JenkinsPlugins2Nix/Parser.hs @@ -26,7 +26,7 @@ import Nix.JenkinsPlugins2Nix.Types runParseManifest :: Text -> Either String Manifest runParseManifest = A.parseOnly parseManifest --- | 'Manifest' parser. +-- | Manifest parser. parseManifest :: A.Parser Manifest parseManifest = do kvs <- kvMap @@ -35,27 +35,30 @@ parseManifest = do Nothing -> Left $ "Could not find " <> Text.unpack k <> " in " <> show kvs Just v -> Right v + getOptionalKey :: Text -> Either String (Maybe Text) + getOptionalKey k = Right (Map.lookup k kvs) + getKeyParsing :: Text -> A.Parser a -> Either String a getKeyParsing k p = getKey k >>= A.parseOnly p eManifest :: Either String Manifest eManifest = do manifest_version' <- getKey "Manifest-Version" - archiver_version' <- optional $ getKey "Archiver-Version" - created_by' <- optional $ getKey "Created-By" - built_by' <- optional $ getKey "Built-By" - build_jdk' <- optional $ getKey "Build-Jdk" - extension_name' <- optional $ getKey "Extension-Name" - specification_title' <- optional $ getKey "Specification-Title" - implementation_title' <- optional $ getKey "Implementation-Title" - implementation_version' <- optional $ getKey "Implementation-Version" - group_id' <- optional $ getKey "Group-Id" + archiver_version' <- getOptionalKey "Archiver-Version" + created_by' <- getOptionalKey "Created-By" + built_by' <- getOptionalKey "Built-By" + build_jdk' <- getOptionalKey "Build-Jdk" + extension_name' <- getOptionalKey "Extension-Name" + specification_title' <- getOptionalKey "Specification-Title" + implementation_title' <- getOptionalKey "Implementation-Title" + implementation_version' <- getOptionalKey "Implementation-Version" + group_id' <- getOptionalKey "Group-Id" short_name' <- getKey "Short-Name" long_name' <- getKey "Long-Name" - url' <- optional $ getKey "Url" + url' <- getOptionalKey "Url" plugin_version' <- getKey "Plugin-Version" - hudson_version' <- optional $ getKey "Hudson-Version" - jenkins_version' <- optional $ getKey "Jenkins-Version" + hudson_version' <- getOptionalKey "Hudson-Version" + jenkins_version' <- getOptionalKey "Jenkins-Version" plugin_dependencies' <- either (\_ -> Right Set.empty) return $ getKeyParsing "Plugin-Dependencies" parsePluginDependencies plugin_developers' <- either (\_ -> Right Set.empty) return $ diff --git a/src/Nix/JenkinsPlugins2Nix/Types.hs b/src/Nix/JenkinsPlugins2Nix/Types.hs index 7d52c63..fd9ddbd 100644 --- a/src/Nix/JenkinsPlugins2Nix/Types.hs +++ b/src/Nix/JenkinsPlugins2Nix/Types.hs @@ -34,6 +34,9 @@ data ResolutionStrategy = data Config = Config { -- | Dependency resolution strategy resolution_strategy :: !ResolutionStrategy + -- | If 'True', do not download or include transitive dependencies. + -- Only generate nix for explicitly requested plugins. + , no_deps :: !Bool -- | User-required plugins. , requested_plugins :: ![RequestedPlugin] -- | Plugin resolution