[
  {
    "path": ".circleci/config.yml",
    "content": "version: 2.1\n\n# Common executor configuration\nexecutors:\n  clojure:\n    docker:\n      - image: circleci/clojure:openjdk-11-lein-2.9.1\n    working_directory: ~/repo\n\n\n# Job definitions\njobs:\n  style:\n    executor: clojure\n    steps:\n      - checkout\n      - run:\n          name: Install cljstyle CLI\n          environment:\n            CLJSTYLE_VERSION: 0.15.0\n          command: |\n            wget https://github.com/greglook/cljstyle/releases/download/${CLJSTYLE_VERSION}/cljstyle_${CLJSTYLE_VERSION}_linux.zip\n            unzip cljstyle_${CLJSTYLE_VERSION}_linux.zip\n      - run:\n          name: Check source formatting\n          command: \"./cljstyle check --report\"\n\n  lint:\n    executor: clojure\n    steps:\n      - checkout\n      - run:\n          name: Install clj-kondo\n          environment:\n            CLJ_KONDO_VERSION: 2022.05.27\n          command: |\n            wget https://github.com/clj-kondo/clj-kondo/releases/download/v${CLJ_KONDO_VERSION}/clj-kondo-${CLJ_KONDO_VERSION}-linux-amd64.zip\n            unzip clj-kondo-${CLJ_KONDO_VERSION}-linux-amd64.zip\n      - run:\n          name: Lint source code\n          command: \"./clj-kondo --lint src test\"\n\n  test:\n    executor: clojure\n    steps:\n      - checkout\n      - restore_cache:\n          keys:\n            - v1-test-{{ checksum \"project.clj\" }}\n            - v1-test-\n      - run: lein deps\n      - run: lein check\n      - run: lein install\n      - run: lein with-profile +ci test2junit\n      - run: rm -r ~/.m2/repository/lein-monolith\n      - store_test_results:\n          path: test-results\n      - save_cache:\n          key: v1-test-{{ checksum \"project.clj\" }}\n          paths:\n            - ~/.m2\n\n  examples:\n    executor: clojure\n    steps:\n      - checkout\n      - restore_cache:\n          keys:\n            - v1-test-{{ checksum \"project.clj\" }}\n            - v1-test-\n      - run:\n          name: Run Example Tests\n          command: ./test/example-tests.sh\n\n\n# Workflow definitions\nworkflows:\n  version: 2\n  build:\n    jobs:\n      - style\n      - lint\n      - test\n      - examples:\n          requires:\n            - test\n"
  },
  {
    "path": ".cljstyle",
    "content": ";; Clojure formatting rules\n;; vim: filetype=clojure\n{:files\n {:ignore #{\"checkouts\" \"target\"}}\n\n :rules\n {:indentation\n  {:indents\n   {config/debug-profile [[:block 1]]\n    d/future-with [[:block 1]]}}}}\n"
  },
  {
    "path": ".github/CODEOWNERS",
    "content": "* @amperity/open-source\n"
  },
  {
    "path": ".gitignore",
    "content": "target/\n/classes\n/checkouts\n/.lein-*\n/.nrepl-port\npom.xml\npom.xml.asc\n*.class\n*.jar\ntest-results\nbuild.xml\n.lsp/\n.clj-kondo/\n!.clj-kondo/config.edn\n.calva/\n"
  },
  {
    "path": ".java-version",
    "content": "1.8\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "Change Log\n==========\n\nAll notable changes to this project will be documented in this file, which\nfollows the conventions of [keepachangelog.com](http://keepachangelog.com/).\nThis project adheres to [Semantic Versioning](http://semver.org/).\n\n## Unreleased\n\n...\n\n## [1.11.0] - 2026-04-17\n\n### Added\n- `deps-on` now supports a `:transitive` option to show all transitive\n  dependents, not just direct ones. Each entry shows the linking dependency\n  that connects it to the tree.\n  [#108](https://github.com/amperity/lein-monolith/pull/108)\n\n### Fixed\n- `deps-of` now correctly filters out external dependencies in non-transitive\n  mode, showing only internal subprojects.\n- `deps-of` now correctly parses the `:transitive` option from the command line.\n\n## [1.10.4] - 2025-02-16\n\n### Fixed\n- Fix performance bottleneck in writing fingerprints files.\n  [#105](https://github.com/amperity/lein-monolith/pull/105)\n\n## [1.10.3] - 2025-08-25\n\n### Fixed\n- Attempted to fix non-deterministic errors in `each :parallel <threads>` by\n  synchronizing namespace loading. These errors existed before, but were made\n  much more likely to occur by to [#102](https://github.com/amperity/lein-monolith/pull/102).\n  [#103](https://github.com/amperity/lein-monolith/pull/103)\n\n## [1.10.2] - 2025-08-25\n\n### Fixed\n- Fixed a bug where `each :parallel <threads>` would run tasks\n  sequentially for the first 10 seconds. [#102](https://github.com/amperity/lein-monolith/pull/102)\n\n\n## [1.10.1] - 2024-10-15\n\n### Changed\n- Subproject `:clean-targets` will automatically be updated with absolute paths to support running\n  the `clean` task as part of `lein monolith each`.\n  [#99](https://github.com/amperity/lein-monolith/pull/99)\n\n\n## [1.10.0] - 2024-06-03\n\n### Added\n- Adds higher order task, `with-dependency-set`, that allows running a task with a\n  dependency set defined in the metaproject.\n  [#97](https://github.com/amperity/lein-monolith/pull/97)\n\n### Changed\n- Dependency sets declared in a subproject profile.clj will override the project\n  `:managed-dependencies` rather than concatenating with the metaproject\n  `:managed-dependencies`.\n  [#97](https://github.com/amperity/lein-monolith/pull/97)\n\n\n## [1.9.1] - 2024-04-17\n\n### Fixed\n- Subproject fingerprints now include `:jar-exclusions` in the calculation.\n  [#95](https://github.com/amperity/lein-monolith/pull/95)\n\n## [1.9.0] - 2023-12-07\n\n### Added\n- Dependency sets can now be defined in the metaproject, and projects can opt into\n  them with the `:monolith/dependency-set` key.\n  [#93](https://github.com/amperity/lein-monolith/pull/93)\n\n\n## [1.8.0] - 2022-07-13\n\n### Added\n- Running `lein monolith changed :debug` will now print lots of extra\n  information about what fingerprint values have changed and why.\n  [#89](https://github.com/amperity/lein-monolith/pull/89)\n- New `lein monolith show-fingerprints` command will print out detailed\n  information about the fingerprint calculations for one or more projects,\n  compared to an existing mark.\n  [#91](https://github.com/amperity/lein-monolith/pull/91)\n\n### Changed\n- Renamed the `master` branch to `main`.\n- Dependency fingerprints are now built from normalized hash trees, so changes\n  to the ordering of dependencies or profiles will no longer change the\n  dependency fingerprint.\n  [#90](https://github.com/amperity/lein-monolith/pull/90)\n\n\n## [1.7.1] - 2021-06-11\n\n### Changed\n- Bump dependency versions.\n\n\n## [1.7.0] - 2021-06-11\n\n### Added\n- The `:project-dirs` pattern can now support recursive subdirectories with\n  a double-wildcard `.../**` syntax.\n  [#85](https://github.com/amperity/lein-monolith/pull/85)\n\n### Fixed\n- Fingerprinting will now correctly track Java files as project source files.\n  [#87](https://github.com/amperity/lein-monolith/pull/87)\n\n\n## [1.6.1] - 2020-10-09\n\n### Changed\n- The `lint` task now only considers dependency names and versions for\n  detecting conflicts, which should improve the signal-to-noise ratio.\n  [#73](https://github.com/amperity/lein-monolith/pull/73)\n- The `unlink` task will now only remove internal checkouts by default. It also\n  accepts an `:all` option to remove external checkouts, as well as a list of\n  project names to specifically unlink.\n  [#66](https://github.com/amperity/lein-monolith/issues/66)\n  [#80](https://github.com/amperity/lein-monolith/pull/80)\n\n### Fixed\n- When the `each` task provides a command to resume execution, the arguments\n  will be properly quoted for shells.\n  [#27](https://github.com/amperity/lein-monolith/issues/27)\n  [#72](https://github.com/amperity/lein-monolith/pull/72)\n- The `each` task is now compatible with composite profiles.\n  [#29](https://github.com/amperity/lein-monolith/issues/29)\n  [#77](https://github.com/amperity/lein-monolith/pull/77)\n- When `each` is used with `:parallel`, task aliases are now correctly resolved\n  before iteration starts.\n  [#36](https://github.com/amperity/lein-monolith/issues/36)\n  [#74](https://github.com/amperity/lein-monolith/pull/74)\n\n### Added\n- The monolith settings can now use `:inherit-raw` and `:inherit-leaky-raw` to\n  list keys which should be inherited without interpretation from the\n  metaproject. This is useful for inheriting source paths without them being\n  canonicalized.\n  [#68](https://github.com/amperity/lein-monolith/issues/68)\n  [#75](https://github.com/amperity/lein-monolith/pull/75)\n- The `each` task supports a new `:silent` option, which will suppress task\n  output for successful projects. This can be useful in large CI builds where\n  the output is only consulted in the event of failure.\n  [#37](https://github.com/amperity/lein-monolith/issues/37)\n  [#81](https://github.com/amperity/lein-monolith/pull/81)\n\n\n## [1.5.0] - 2020-09-17\n\n### Added\n- Subproject fingerprints now includes the Java version in the calculation.\n  [#71](https://github.com/amperity/lein-monolith/pull/71)\n\n\n## [1.4.0] - 2019-11-08\n\n### Fixed\n- When an exception is thrown during an `each :endure` iteration, the stack\n  trace is printed immediately instead of swallowing the error.\n- When an exception is thrown during an `each :output` iteration, the stack\n  trace is printed in the output file in addition to the combined output.\n  [#56](https://github.com/amperity/lein-monolith/pull/56)\n- Subtasks of `do` are resolved before parallel execution, which should ensure\n  they are fully loaded before they are called.\n- Prevent a potential race condition when combining the `:parallel` and\n  `:output` options in the `each` subtask.\n\n### Added\n- The `graph` subtask supports an `:image-path` option to explicitly specify the\n  graph image output, as well as a `:dot-path` option to also write the raw dot\n  definition for the graph.\n- New `deps` subtask supports listing all project dependencies in the monorepo.\n  The output should be suitable for other tooling to consume.\n\n\n## [1.3.2] - 2019-10-21\n\n### Fixed\n- Subproject dependency calculation now includes dependencies declared in the\n  project's profiles.\n  [#51](https://github.com/amperity/lein-monolith/pull/51)\n\n\n## [1.3.1] - 2019-10-14\n\n### Fixed\n- Subproject fingerprints now include the project's artifact version in the\n  calculation.\n\n### Added\n- Subprojects may include a `:monolith/fingerprint-seed` value as a way to force\n  fingerprint invalidations when desired.\n\n\n## [1.3.0] - 2019-10-07\n\n### Changed\n- Remove dependency on `puget` for colorized output and canonical printing. This\n  avoids pulling in `fipp` which is problematic to use in Leiningen on Java 9+.\n  [#49](https://github.com/amperity/lein-monolith/pull/49)\n\n### Added\n- Allow ANSI color output to be disabled by setting the `LEIN_MONOLITH_COLOR`\n  environment variable to `no`, `false`, or `off`.\n\n\n## [1.2.2] - 2019-09-11\n\n### Fixed\n- Ensure projects are not initialized concurrently to guard against \"unbound fn\"\n  errors.\n  [#15](https://github.com/amperity/lein-monolith/issues/15)\n  [#48](https://github.com/amperity/lein-monolith/pull/48)\n\n### Changed\n- Adopted cljfmt style rules and added CI style checks.\n\n\n## [1.2.1] - 2019-04-25\n\n### Added\n- Allow the `graph` subtask to take specific targeting options.\n  [#43](https://github.com/amperity/lein-monolith/pull/43)\n\n### Fixed\n- The `each` subtask couldn't be composed with subsequent tasks if it had no\n  work to do. [#44](https://github.com/amperity/lein-monolith/pull/44)\n\n\n## [1.2.0] - 2019-01-08\n\n### Changed\n- The `each` subtask no longer fails when zero projects are selected.\n\n### Added\n- The `each` subtask supports `:refresh` and `:changed` to perform incremental\n  runs over the projects.\n- New `changed`, `mark-fresh`, and `clear-fingerprints` subtasks inspect and\n  manipulate the underlying fingerprints used to perform incremental runs.\n\n### Fixed\n- `link` could try to link a project to itself and fail. [#41](https://github.com/amperity/lein-monolith/pull/41)\n- Bumped puget version to 1.0.3 to support JDK 11.\n\n\n## [1.1.0] - 2018-08-17\n\n### Added\n- The `link` subtask accepts a list of projects to target, allowing you to\n  select which checkout links get created.\n\n### Fixed\n- The `graph` subtask could throw an exception when clusters exist at the root\n  of the metaproject. [#31](https://github.com/amperity/lein-monolith/pull/31)\n\n\n## [1.0.1] - 2017-05-22\n\n### Added\n- Metaprojects can specify an `:inherit-leaky` vector to generate a leaky\n  profile for inclusion in subprojects' built artifacts.\n\n\n## [1.0.0]\n\nThis release marks the first stable major release of the plugin. Actual feature\nchanges are small, but `lein-monolith` has seen enough production use to be\nconsidered a mature project.\n\n### Added\n- New `:output` option to `each` task allows saving individual subproject output\n  to separate files under the given directory.\n\n### Fixed\n- Tasks run with `each` now use the subproject's root as the working directory,\n  rather than the monolith root. [#21](https://github.com/amperity/lein-monolith/issues/21)\n\n\n## [0.3.2] - 2017-03-21\n\n### Changed\n- Abstracted targeting options to generalize to multiple tasks.\n\n### Added\n- The `info` task supports targeting options.\n- The `with-all` task supports targeting options.\n\n### Removed\n- Drop deprecated `:subtree` targeting option.\n\n\n## [0.3.1] - 2016-12-14\n\n### Added\n- Options taking a subproject name now support omitting the namespace component\n  if only one project has that name.\n- The `each` task supports additional dependency closure selection options,\n  including `:in <projects>`, `:upstream`, `:upstream-of <projects>`,\n  `:downstream`, and `:downstream-of <projects>`.\n- Multiple `:project-selectors` can be provided to `each` in order to chain the\n  filters applied to the targeted projects.\n\n### Changed\n- Option parsing is handled more uniformly within the plugin.\n- The `:subtree` option to `each` is deprecated.\n\n### Fixed\n- Resolved a potential issue where filtering the targeted subprojects could\n  result in invalid parallel execution order.\n\n\n## [0.3.0] - 2016-09-16\n\n### Added\n- Add `:report` option to the `each` task to print out detailed timing once\n  `each` completes.\n- Add `each :parallel <threads>` option to run tasks on subprojects\n  concurrently. Tasks still run in dependency order, but mutually independent\n  projects are run simultaneously on a fixed-size thread pool.\n\n### Changed\n- Modify `each` to print a completion message after subproject tasks finish\n  running. This improves output during parallel execution.\n\n\n## [0.2.3] - 2016-08-15\n\n### Added\n- The `each` task supports an `:endure` option to continue iteration in the\n  event of subproject failures. This supports better CI usage for testing.\n\n\n## [0.2.2] - 2016-08-08\n\n### Added\n- The `each` task now adds a `:monolith/index` key to project maps passed to\n  the project-selector function to enable mod-distribution logic.\n\n\n## [0.2.1] - 2016-08-05\n\n### Changed\n- Split up subtasks into separate namespaces to improve code readability.\n\n### Fixed\n- Fix bug where options to `each` weren't output in the continuation command.\n\n### Added\n- Add `:deep` option to the `link` task to link checkouts for all transitive\n  dependencies.\n- Add explicit request for garbage-collection before running subproject tasks in\n  `each` iteration.\n- Warn when `with-all` is used in a subproject.\n\n\n## [0.2.0] - 2016-07-20\n\nThis release contains a **breaking change** in how the plugin is configured! All\noptions are now contained in a required metaproject at the repository root\ninstead of a separate `monolith.clj` file.\n\nThis should also be much faster to run due to lazily initializing subprojects\ninstead of loading them all before running any commands.\n\n### Changed\n- Moved monolith configuration into metaproject definition.\n- Subprojects are loaded lazily, resulting in dramatically reduced latency\n  before plugin tasks are executed.\n- The merged profile now includes `:resource-paths` from each subproject.\n- The merged profile no longer merges all dependencies; instead, each subproject\n  is included in the profile and dependencies are resolved transitively.\n\n### Added\n- Metaproject configuration may be inherited using the `:monolith/inherit` key\n  in subprojects and `:monolith {:inherit [...]}` in the metaproject.\n- New `lint` subtask runs the dependency conflict checks which previously ran\n  during every merged profile task.\n- Added unit tests and continuous-integration via CircleCI.\n- `each` task supports `:skip <project>` and `:select <filter>` options.\n\n### Removed\n- Setting the `:monolith` key in a project no longer automatically includes the\n  merged profile; instead, it is used for general plugin configuration.\n\n\n## [0.1.1] - 2016-07-08\n\n### Fixed\n- Fixed a bug in which `each :subtree` would show the wrong number of total\n  subprojects while printing its progress.\n- Internal projects are now implicit dependencies of the merged monolith\n  profile.\n\n\n## 0.1.0 - 2016-07-07\n\nInitial project release\n\n\n[Unreleased]: https://github.com/amperity/lein-monolith/compare/1.11.0...HEAD\n[1.11.0]: https://github.com/amperity/lein-monolith/compare/1.10.4...1.11.0\n[1.10.4]: https://github.com/amperity/lein-monolith/compare/1.10.3...1.10.4\n[1.10.3]: https://github.com/amperity/lein-monolith/compare/1.10.2...1.10.3\n[1.10.2]: https://github.com/amperity/lein-monolith/compare/1.10.1...1.10.2\n[1.10.1]: https://github.com/amperity/lein-monolith/compare/1.10.0...1.10.1\n[1.10.0]: https://github.com/amperity/lein-monolith/compare/1.9.1...1.10.0\n[1.9.1]: https://github.com/amperity/lein-monolith/compare/1.9.0...1.9.1\n[1.9.0]: https://github.com/amperity/lein-monolith/compare/1.8.0...1.9.0\n[1.8.0]: https://github.com/amperity/lein-monolith/compare/1.7.1...1.8.0\n[1.7.1]: https://github.com/amperity/lein-monolith/compare/1.7.0...1.7.1\n[1.7.0]: https://github.com/amperity/lein-monolith/compare/1.6.1...1.7.0\n[1.6.1]: https://github.com/amperity/lein-monolith/compare/1.5.0...1.6.1\n[1.5.0]: https://github.com/amperity/lein-monolith/compare/1.4.0...1.5.0\n[1.4.0]: https://github.com/amperity/lein-monolith/compare/1.3.2...1.4.0\n[1.3.2]: https://github.com/amperity/lein-monolith/compare/1.3.1...1.3.2\n[1.3.1]: https://github.com/amperity/lein-monolith/compare/1.3.0...1.3.1\n[1.3.0]: https://github.com/amperity/lein-monolith/compare/1.2.2...1.3.0\n[1.2.2]: https://github.com/amperity/lein-monolith/compare/1.2.1...1.2.2\n[1.2.1]: https://github.com/amperity/lein-monolith/compare/1.2.0...1.2.1\n[1.2.0]: https://github.com/amperity/lein-monolith/compare/1.1.0...1.2.0\n[1.1.0]: https://github.com/amperity/lein-monolith/compare/1.0.1...1.1.0\n[1.0.1]: https://github.com/amperity/lein-monolith/compare/1.0.0...1.0.1\n[1.0.0]: https://github.com/amperity/lein-monolith/compare/0.3.2...1.0.0\n[0.3.2]: https://github.com/amperity/lein-monolith/compare/0.3.1...0.3.2\n[0.3.1]: https://github.com/amperity/lein-monolith/compare/0.3.0...0.3.1\n[0.3.0]: https://github.com/amperity/lein-monolith/compare/0.2.3...0.3.0\n[0.2.3]: https://github.com/amperity/lein-monolith/compare/0.2.2...0.2.3\n[0.2.2]: https://github.com/amperity/lein-monolith/compare/0.2.1...0.2.2\n[0.2.1]: https://github.com/amperity/lein-monolith/compare/0.2.0...0.2.1\n[0.2.0]: https://github.com/amperity/lein-monolith/compare/0.1.1...0.2.0\n[0.1.1]: https://github.com/amperity/lein-monolith/compare/0.1.0...0.1.1\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright 2016-2022 Amperity, Inc.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n  http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n"
  },
  {
    "path": "README.md",
    "content": "lein-monolith\n=============\n\n[![CircleCI](https://circleci.com/gh/amperity/lein-monolith.svg?style=shield&circle-token=e57a92e79aa9113f1950498cbeeb0880c3f587d3)](https://circleci.com/gh/amperity/lein-monolith/tree/main)\n\n`lein-monolith` is a Leiningen plugin to work with multiple projects inside a\nmonorepo. At a high level, the plugin gives you a way to:\n- Share configuration across subprojects, such as `:repositories`,\n  `:managed-dependencies`, `:env`, etc.\n- Run tasks across a multiple projects matching sophisticated selection\n  criteria.\n- Run tasks across a globally-merged view of multiple projects.\n- Query dependencies, generate graphs, and other utilities.\n\nFor a more detailed introduction to the project and some motivation, see this\n[2016 Seajure presentation](https://docs.google.com/presentation/d/1jqYG2N2YalWdVG4oDqs1mua4hOyxVD_nejANrg6h8to/present).\n\n## Installation\n\nLibrary releases are published on Clojars. To use the latest version with\nLeiningen, add the following plugin to your user profile or project\ndefinitions:\n\n[![Clojars Project](http://clojars.org/lein-monolith/lein-monolith/latest-version.svg)](http://clojars.org/lein-monolith/lein-monolith)\n\n## Configuration\n\nThe `monolith` task provides several commands to make working with monorepos\neasier. In order to use them, you'll need to create some configuration telling\nthe plugin where your subprojects are.\n\nThe configuration is provided by a _metaproject_, which lives in the repository\nroot and must contain a value for the `:monolith` project key. Create a\ntop-level [`project.clj`](example/project.clj) file and add the plugin and\nmonolith entries.\n\nSee the [configuration docs](doc/config.md) for more details about the available\noptions.\n\n## Usage\n\n`lein-monolith` can be used inside the individual projects within the monorepo,\nor you can use it from the repository root to operate on all the subprojects\ntogether.\n\n### Targeting Options\n\nMany tasks which operate on multiple projects accept targeting options, which\ngenerally select or filter the command to a subset of the subprojects.\n\n- `:in <names>`             Add the named projects directly to the targets.\n- `:upstream`               Add the transitive dependencies of the current project to the targets.\n- `:upstream-of <names>`    Add the transitive dependencies of the named projects to the targets.\n- `:downstream`             Add the transitive consumers of the current project to the targets.\n- `:downstream-of <names>`  Add the transitive consumers of the named projects to the targets.\n- `:select <key>`           Use a selector from the config to filter target projects.\n- `:skip <names>`           Exclude one or more projects from the target set.\n\nEach `<names>` argument can contain multiple comma-separated project names, and\nall the targeting options may be provided multiple times.\n\n### Subproject Info\n\nTo see a list of all the projects that lein-monolith knows about, you can use\nthe `info` task:\n\n```\nlein monolith info [:bare] [targets]\n```\n\nThis will print out the config file location, coordinates of every subproject\nfound, and a relative path to their location within the repo. For scripting, you\ncan pass the `:bare` flag, which will restrict the output to just the project\nname and path.\n\nThe plugin also provides the `deps-on` task to query which subprojects have a\ncertain dependency:\n\n```\nlein monolith deps-on lib-b\n```\n\nOr you can go the other way with `deps-of` to find the subprojects which a\ncertain project depends on:\n\n```\nlein monolith deps-of app-a\n```\n\n### Subproject Iteration\n\nA useful higher-order task is `each`, which will run the following commands on\nevery subproject in the repo, in dependency order. That means that projects\nwhich don't depend on any other internal projects will run first, letting you do\nthings like:\n\n```\nlein monolith each check\nlein monolith each :upstream :parallel 4 install\nlein monolith each :start my-lib/foo do check, test\nlein monolith each :select :deployable uberjar\n```\n\nIn addition to targeting options, `each` accepts:\n\n- `:start <name>` provide a starting point for the subproject iteration.\n- `:parallel` run subproject tasks concurrently, up to the number of specified\n  threads.\n- `:endure` continue applying the task to every subproject, even if one fails.\n  If any projects fail, the command will still exit with a failure status. This\n  is useful in situations such as CI tests, where you don't want a failure to\n  halt iteration.\n- `:report` show a detailed timing report after the tasks finish executing.\n- `:silent` suppress task output for successful projects.\n- `:output` path to a directory to save individual build output in.\n\n#### Incremental Builds\n\nThe `:refresh` option only visits projects that have changed since the last\n`:refresh`. This allows incrementally building your projects:\n\n```\nlein monolith each :refresh ci/build install\n```\n\nThe project is only considered refreshed if the task is successful. This means\nyou can run tests over the projects that have changed since the last\n_successful_ test run:\n\n```\nlein monolith each :refresh ci/test test\n```\n\nBehind the scenes, lein-monolith is storing hashed fingerprints of each project,\nwhich you can inspect and manually manipulate:\n\n```\nlein monolith changed\nlein monolith mark-fresh :upstream ci/build\nlein monolith clear-fingerprints ci/test\n```\n\n### Merged Source Profile\n\nThe plugin can create a profile with `:resource-paths`, `:source-paths` and\n`:test-paths` updated to include the source and test files from multiple\nprojects in the monorepo. The profile also sets `:dependencies` on each internal\nproject, giving you a closure of all dependencies across all the subprojects.\n\nThis can be useful for running lint and tests on multiple subprojects at once:\n\n```\nlein monolith with-all [targeting] test\n```\n\n### Checkout Links\n\nThe `link` task creates\n[checkout](https://github.com/technomancy/leiningen/blob/stable/doc/TUTORIAL.md#checkout-dependencies)\nsymlinks to all the internal packages that this project depends on. The plugin\nalso includes an `unlink` task as a convenience method for removing checkout\ndependencies.\n\nTo link checkouts for all transitive dependencies, you can pass the `:deep` option.\n\nIf you have existing checkout links which conflict, you'll get warnings. To\noverride them, you can pass the `:force` option.\n\n```\nlein monolith link [:deep :force] [project...]\nlein monolith unlink [:all] [project...]\n```\n\nIn general, it's recommended to only link between the projects you're actually\nactively working on, otherwise Leiningen has to recursively trace the full tree\nof checkouts before running things.\n\n\n## License\n\nLicensed under the Apache License, Version 2.0. See the [LICENSE](LICENSE) file\nfor rights and restrictions.\n"
  },
  {
    "path": "doc/config.md",
    "content": "Monolith Configuration\n======================\n\nThis document lays out the various configuration options available in\n`lein-monolith`.\n\n## Subproject Locations\n\nThe `:project-dirs` key tells monolith where to find the projects inside the\nrepo by giving a vector of relative paths. Each entry should point to either a\ndirect subproject directory (containing a `project.clj` file) such as\n`apps/app-a`, or end with a wildcard `*` to indicate that all child directories\nshould be searched for projects, like `libs/*`. Note that this only works with a\nsingle level of wildcard matching at the end of the path.\nIf you would like to search recursively you can indicate that using `lib/**`\nand it will search for all subdirectories containing a `project.clj` file.\n\n## Config Inheritance\n\nIn order to share common project definition entries, you can also set the\n`:inherit` key to a vector of attributes which should be inherited by\nsubprojects. In each subproject where you want this behavior, add a\n`:monolith/inherit` key.\n\nA value of `true` will merge in a profile with the attributes set in the\nmetaproject. Alternately, you can provide a vector of additional keys to merge\nfrom the metaproject. Attaching `^:replace` metadata will cause the vector to\noverride the attributes set in the metaproject.\n\nSome metaproject settings are only useful if they are still present in the\ngenerated build artifacts for the subprojects. The primary examples of these are\n`:repositories` and `:managed-dependencies`. For these, you can specify a second\nkey in the metaproject called `:inherit-leaky`, which follows the format of\n`:inherit` above. Properties in this profile will be included in the built JAR\nand pom files for the subproject.\n\nLastly, `lein-monolith` supports inheriting unprocessed values, via\n`:inherit-raw` and `:inherit-leaky-raw`. These are of particular use when\ninheriting paths, as Leiningen absolutizes paths upon processing a project map.\nBy using raw inheritance, you can safely inherit paths, e.g. `:test-paths` or\n`:source-paths`.\n\n## Dependency Sets\n\nThe `:dependency-sets` key can be configured in the metaproject to provide child\nprojects with a curated set of managed dependencies to opt into instead of using\na single list of managed dependencies in the metaproject. This should be a map of\ndependency set names and their dependencies.\n\nFor example, in the metaproject file we can define a dependency set\ncalled `:set-a` as follows:\n\n```clj\n(defproject lein-monolith.example/all \"MONOLITH\"\n\n...\n\n:monolith\n  {:dependency-sets\n   {:set-a\n    [[amperity/greenlight \"0.7.1\"]\n     [org.clojure/spec.alpha \"0.3.218\"]]}\n\n...\n\n)\n```\n\nThe `:monolith/dependency-set` key can then be used to a opt child project into `:set-a` as follows:\n\n```clj\n(defproject lein-monolith.example/app-a \"MONOLITH-SNAPSHOT\"\n\n ...\n\n :monolith/dependency-set :set-a\n\n ...\n\n)\n```\n\nBy selecting a dependency set from the metaproject with `:monolith/dependency-set`,\nyou will merge in a profile with `:managed-dependencies` set to the dependencies within\nthe dependency set. If you also configure the child project to use inherited profiles, this profile\nwill be merged in *before* the inherited profiles. This means that dependency versions in\na dependency set will have precedence over versions in an inherited `:managed-dependencies` key.\n\nDependency set evaluation can also be forced using the `with-dependency-set` task:\n\n```sh\nlein monolith with-dependency-set :foo ...\n```\n\nThis works by adding and activating a profile that will replace the `:managed-dependencies` profile\nwith the named set dependency coordinates, similar to adding the `:monolith/dependency-set` key to\nthe project file.\n\nThis can be combined with other higher-order tasks (e.g., `each`) to do preliminary testing throughout\nthe entire repository without having to manually change each project file. For combining with other\nmonolith tasks, `with-dependency-set` should be a sub-task of the task you want to do. For example:\n\n```sh\nlein monolith each ... monolith with-dependency-set :foo test\n```\n\nIt is also useful to run on the metaproject for tasks that inspect dependencies, e.g.\n[antq](https://github.com/liquidz/antq) or [lein-ancient](https://github.com/xsc/lein-ancient), but\nare not aware of monolith dependency set definitions.\n\nFor example, scanning the example project with `antq`, we will see the `:current` version of the\ndependency set that override the managed dependencies declared in the project:\n```sh\nlein antq\n[##################################################] 15/15\n| :file       | :name                       | :current        | :latest |\n|-------------+-----------------------------+-----------------+---------|\n| project.clj | amperity/greenlight         | 0.6.0           | 0.7.1   | # Older version\n|             | com.amperity/vault-clj      | 2.1.583         | 2.2.586 | # Not in dependency set\n|             | org.clojure/clojure         | 1.10.1          | 1.11.3  | # Global dependency\n\n\nlein monolith with-dependency-set :set-outdated antq\n[##################################################] 15/15\n| :file       | :name                       | :current        | :latest |\n|-------------+-----------------------------+-----------------+---------|\n| project.clj | amperity/greenlight         | 0.7.0           | 0.7.1   | # Version from dependency set\n|             | org.clojure/spec.alpha      | 0.2.194         | 0.5.238 | # Added dependency from set\n|             | org.clojure/clojure         | 1.10.1          | 1.11.3  | # Retains global dependency\n```\n"
  },
  {
    "path": "doc/releasing.md",
    "content": "# Releasing\n\n1. Update the version number in these places:\n\n   - [project.clj](./project.clj)\n   - [example/project.clj](./example/project.clj)\n\n1. Update [CHANGELOG.md](./CHANGELOG.md). We follow the guidelines from\n   [keepachangelog.com](http://keepachangelog.com/) and [Semantic\n   Versioning](http://semver.org/)\n\n1. Commit changes, create a PR, merge the PR into `main`.\n\n1. Create a signed tag at the release commit. `git tag -s X.X.X -m \"X.X.X\n   Release\" && git push X.X.X`\n\n1. From the release commit, run `lein deploy clojars`, which will build and\n   upload the plugin jar to the Clojars repository. You will need to be a member\n   of the `lein-monolith` [Clojars group](https://clojars.org/groups/lein-monolith).\n"
  },
  {
    "path": "example/.gitignore",
    "content": "/target\n/checkouts\n/.lein-*\n/.nrepl-port\n/*.bak\n"
  },
  {
    "path": "example/apps/app-a/.gitignore",
    "content": "/target\n/classes\n/checkouts\npom.xml\npom.xml.asc\n*.jar\n*.class\n/.lein-*\n/.nrepl-port\n"
  },
  {
    "path": "example/apps/app-a/project.clj",
    "content": "(defproject lein-monolith.example/app-a \"MONOLITH-SNAPSHOT\"\n  :description \"Example project with internal and external dependencies.\"\n  :monolith/inherit true\n  :monolith/dependency-set :set-a\n  :deployable true\n\n  :dependencies\n  [[org.clojure/clojure \"1.10.1\"]\n   [commons-io \"2.5\"]\n   [lein-monolith.example/lib-a \"MONOLITH-SNAPSHOT\"]\n   [lein-monolith.example/lib-b \"MONOLITH-SNAPSHOT\"]]\n\n  :profiles\n  {:shared {:source-paths [\"bench\"]}\n   :dev [:shared {:dependencies [[clj-stacktrace \"0.2.8\"]]}]\n   :uberjar [:shared {:dependencies [[commons-net \"3.6\"]]}]})\n"
  },
  {
    "path": "example/apps/app-a/src/project_a/core.clj",
    "content": "(ns project-a.core)\n\n\n(defn foo\n  \"I don't do a whole lot.\"\n  [x]\n  (println x \"Hello, World!\"))\n"
  },
  {
    "path": "example/apps/app-a/test/project_a/core_test.clj",
    "content": "(ns project-a.core-test\n  (:require\n    [clojure.test :refer :all]\n    [project-a.core :refer :all]))\n\n\n(deftest a-app-test\n  (is true))\n"
  },
  {
    "path": "example/libs/lib-a/.gitignore",
    "content": "/target\n/classes\n/checkouts\npom.xml\npom.xml.asc\n*.jar\n*.class\n/.lein-*\n/.nrepl-port\n"
  },
  {
    "path": "example/libs/lib-a/project.clj",
    "content": "(defproject lein-monolith.example/lib-a \"MONOLITH-SNAPSHOT\"\n  :description \"Example library with no internal dependencies.\"\n  :monolith/inherit true\n\n  :dependencies\n  [[org.clojure/clojure \"1.10.1\"]])\n"
  },
  {
    "path": "example/libs/lib-a/src/lib_a/core.clj",
    "content": "(ns lib-a.core)\n\n\n(defn foo\n  \"I don't do a whole lot.\"\n  [x]\n  (println x \"Hello, World!\"))\n"
  },
  {
    "path": "example/libs/lib-a/test/lib_a/core_test.clj",
    "content": "(ns lib-a.core-test\n  (:require\n    [clojure.test :refer :all]\n    [lib-a.core :refer :all]))\n\n\n(deftest a-code-test\n  (is true))\n"
  },
  {
    "path": "example/libs/lib-b/.gitignore",
    "content": "/target\n/classes\n/checkouts\npom.xml\npom.xml.asc\n*.jar\n*.class\n/.lein-*\n/.nrepl-port\n"
  },
  {
    "path": "example/libs/lib-b/project.clj",
    "content": "(defproject lein-monolith.example/lib-b \"MONOLITH-SNAPSHOT\"\n  :description \"Example lib depending on lib-a.\"\n  :monolith/inherit [:aliases]\n\n  :dependencies\n  [[org.clojure/clojure \"1.10.1\"]\n   [lein-monolith.example/lib-a \"MONOLITH-SNAPSHOT\"]]\n\n  :clean-targets ^{:protect false} [\"target\" \"resources\"])\n"
  },
  {
    "path": "example/libs/lib-b/src/lib_b/core.clj",
    "content": "(ns lib-b.core)\n\n\n(defn foo\n  \"I don't do a whole lot.\"\n  [x]\n  (println x \"Hello, World!\"))\n"
  },
  {
    "path": "example/libs/lib-b/test/lib_b/core_test.clj",
    "content": "(ns lib-b.core-test\n  (:require\n    [clojure.test :refer :all]\n    [lib-b.core :refer :all]))\n\n\n(deftest b-code-test\n  (testing \"FIXME, I fail.\"\n    (is false)))\n"
  },
  {
    "path": "example/libs/lib-d/project.clj",
    "content": "(defproject lein-monolith.example/lib-d \"MONOLITH-SNAPSHOT\"\n  :description \"Example lib depending on lib-b, for testing transitive deps-on.\"\n  :monolith/inherit [:aliases]\n\n  :dependencies\n  [[org.clojure/clojure \"1.10.1\"]\n   [lein-monolith.example/lib-b \"MONOLITH-SNAPSHOT\"]])\n"
  },
  {
    "path": "example/libs/lib-d/src/lib_d/core.clj",
    "content": "(ns lib-d.core\n  \"Example library depending on lib-b.\")\n"
  },
  {
    "path": "example/libs/subdir/lib-c/.gitignore",
    "content": "/target\n/classes\n/checkouts\npom.xml\npom.xml.asc\n*.jar\n*.class\n/.lein-*\n/.nrepl-port\n"
  },
  {
    "path": "example/libs/subdir/lib-c/project.clj",
    "content": "(defproject lein-monolith.example/lib-c \"MONOLITH-SNAPSHOT\"\n  :description \"Example lib depending on lib-a.\"\n  :monolith/inherit [:aliases]\n\n  :dependencies\n  [[org.clojure/clojure \"1.10.1\"]\n   [lein-monolith.example/lib-a \"MONOLITH-SNAPSHOT\"]])\n"
  },
  {
    "path": "example/libs/subdir/lib-c/src/lib_c/core.clj",
    "content": "(ns lib-c.core)\n\n\n(defn foo\n  \"I don't do a whole lot.\"\n  [x]\n  (println x \"Hello, World!\"))\n"
  },
  {
    "path": "example/project.clj",
    "content": "(defproject lein-monolith.example/all \"MONOLITH\"\n  :description \"Overarching example project.\"\n\n  :aliases\n  {\"version+\" [\"version\"]\n   \"version++\" [\"version+\"]}\n\n  :plugins\n  [[lein-monolith \"1.11.0\"]\n   [lein-pprint \"1.2.0\"]]\n\n  :dependencies\n  [[org.clojure/clojure \"1.10.1\"]]\n\n  :managed-dependencies\n  [[amperity/greenlight \"0.6.0\"]\n   [com.amperity/vault-clj \"2.1.583\"]]\n\n  :test-selectors\n  {:unit (complement :integration)\n   :integration :integration}\n\n  :test-paths ^:replace\n  [\"test/unit\"\n   \"test/integration\"]\n\n  :compile-path\n  \"%s/compiled\"\n\n  :monolith\n  {:inherit\n   [:aliases\n    :test-selectors\n    :env]\n\n   :inherit-raw\n   [:test-paths]\n\n   :inherit-leaky\n   [:repositories\n    :managed-dependencies]\n\n   :inherit-leaky-raw\n   [:compile-path]\n\n   :project-selectors\n   {:deployable :deployable\n    :unstable #(= (first (:version %)) \\0)}\n\n   :project-dirs\n   [\"apps/*\"\n    \"libs/**\"\n    \"not-found\"]\n\n   :dependency-sets\n   {:set-outdated\n    [[amperity/greenlight \"0.7.0\"]\n     [org.clojure/spec.alpha \"0.2.194\"]]\n    :set-a\n    [[amperity/greenlight \"0.7.1\"]\n     [org.clojure/spec.alpha \"0.3.218\"]]}}\n\n  :env\n  {:foo \"bar\"})\n"
  },
  {
    "path": "project.clj",
    "content": "(defproject lein-monolith \"1.11.0\"\n  :description \"Leiningen plugin for managing subrojects within a monorepo.\"\n  :url \"https://github.com/amperity/lein-monolith\"\n  :license {:name \"Apache License 2.0\"\n            :url \"http://www.apache.org/licenses/LICENSE-2.0\"}\n\n  :eval-in-leiningen true\n  :deploy-branches [\"main\"]\n\n  :dependencies\n  [[manifold \"0.2.4\"]\n   [rhizome \"0.2.9\"]]\n\n  :hiera\n  {:vertical false\n   :show-external true\n   :cluster-depth 1\n   :ignore-ns #{clojure manifold}}\n\n  :profiles\n  {:dev\n   {:plugins [[lein-cloverage \"1.2.4\"]]\n    :dependencies [[org.clojure/clojure \"1.10.3\"]]}\n\n   :ci\n   {:plugins [[test2junit \"1.4.2\"]]\n    :test2junit-silent true\n    :test2junit-output-dir \"test-results/clojure.test\"}})\n"
  },
  {
    "path": "src/lein_monolith/color.clj",
    "content": "(ns lein-monolith.color\n  \"Coloring functions which apply ANSI color codes to color terminal output.\"\n  (:require\n    [clojure.string :as str]))\n\n\n(def ^:private sgr-code\n  \"Map of symbols to numeric SGR (select graphic rendition) codes.\"\n  {:none        0\n   :bold        1\n   :underline   3\n   :blink       5\n   :reverse     7\n   :hidden      8\n   :strike      9\n   :black      30\n   :red        31\n   :green      32\n   :yellow     33\n   :blue       34\n   :magenta    35\n   :cyan       36\n   :white      37\n   :fg-256     38\n   :fg-reset   39\n   :bg-black   40\n   :bg-red     41\n   :bg-green   42\n   :bg-yellow  43\n   :bg-blue    44\n   :bg-magenta 45\n   :bg-cyan    46\n   :bg-white   47\n   :bg-256     48\n   :bg-reset   49})\n\n\n(def enabled?\n  \"Delay which yields true if text should be rendered with color.\"\n  (delay\n    (let [env (System/getenv \"LEIN_MONOLITH_COLOR\")\n          disabled?  (and env (contains? #{\"no\" \"false\" \"off\"}\n                                         (str/lower-case env)))]\n      (not disabled?))))\n\n\n(defn- sgr\n  \"Returns an ANSI escope string which will apply the given collection of SGR\n  codes.\"\n  [codes]\n  (let [codes (map sgr-code codes codes)\n        codes (str/join \\; codes)]\n    (str \\u001b \\[ codes \\m)))\n\n\n(defn colorize\n  \"Wraps the given string with SGR escapes to apply the given codes, then reset\n  the graphics. If `*enabled*` is not truthy, returns the string unaltered.\"\n  [codes string]\n  (if @enabled?\n    (let [codes (if (keyword? codes)\n                  [codes]\n                  (vec codes))]\n      (str (sgr codes) string (sgr [:none])))\n    string))\n"
  },
  {
    "path": "src/lein_monolith/config.clj",
    "content": "(ns lein-monolith.config\n  (:require\n    [clojure.java.io :as io]\n    [lein-monolith.dependency :as dep]\n    [leiningen.core.main :as lein]\n    [leiningen.core.project :as project])\n  (:import\n    java.io.File))\n\n\n(defmacro debug-profile\n  \"Measure the time to compute the value in `body`, printing out a debug\n  message with the total elapsed time.\"\n  [message & body]\n  `(let [start# (System/nanoTime)\n         message# ~message\n         value# (do ~@body)\n         elapsed# (/ (- (System/nanoTime) start#) 1000000.0)]\n     (lein/debug \"Elapsed:\" message# (format \"=> %.3f ms\" elapsed#))\n     value#))\n\n\n;; ## General Configuration\n\n(defn- find-up\n  \"Searches upward from the given root directory, locating files with the given\n  name. Returns a sequence of `File` objects in the order they occur in the\n  parents of `root`.\"\n  [root file-name]\n  (when root\n    (lazy-seq\n      (let [dir (io/file root)\n            file (io/file dir file-name)\n            next-files (find-up (.getParentFile dir) file-name)]\n        (if (.exists file)\n          (cons file next-files)\n          next-files)))))\n\n\n(defn- attach-raw-to-meta\n  \"Attaches the raw monolith project as metadata to an initialized project.\n  It's necessary to have a reference to the raw project so unprocessed values\n  can be inherited with inherit-raw and inherit-leaky-raw.\"\n  [monolith raw-project]\n  (vary-meta monolith assoc :monolith/raw raw-project))\n\n\n(defn find-monolith\n  \"Returns the loaded project map for the monolith metaproject, or nil if not\n  found.\n\n  If the given project already has the `:monolith` key, it's returned directly.\n  Otherwise, parent directories are searched using `find-up` and any projects\n  are loaded to check for the `:monolith` entry.\"\n  [project]\n  (if (:monolith project)\n    (let [project-file (io/file (:root project) \"project.clj\")\n          raw-project (project/read-raw (str project-file))]\n      (attach-raw-to-meta project raw-project))\n    (some (fn check-project\n            [file]\n            (lein/debug \"Reading candidate project file\" (str file))\n            (let [super (project/read-raw (str file))]\n              (when (:monolith super)\n                (attach-raw-to-meta super super))))\n          (find-up (:root project) \"project.clj\"))))\n\n\n(defn find-monolith!\n  \"Returns the loaded project map for the monolith metaproject. Aborts with an\n  error if not found.\"\n  [project]\n  (let [monolith (debug-profile \"find-monolith\" (find-monolith project))]\n    (when-not monolith\n      (lein/abort \"Could not find monolith project in any parent directory of\"\n                  (:root project)))\n    (lein/debug \"Found monolith project rooted at\" (:root monolith))\n    monolith))\n\n\n;; ## Subproject Configuration\n\n(defn- project-dir?\n  \"Given a directory returns true if it contains a `project.clj` file\"\n  [dir]\n  (.exists (io/file dir \"project.clj\")))\n\n\n(defn- all-projects\n  \"Given a directory recursively search for all sub directories that contain\n  a `project.clj` file and return that list.\"\n  [dir]\n  (let [subdirs (->> (.listFiles dir)\n                     (filter #(.isDirectory ^File %)))\n        grouped (group-by project-dir? subdirs)\n        top-projects (get grouped true)\n        all-subdir-project (mapcat all-projects (get grouped false))]\n    (concat top-projects all-subdir-project)))\n\n\n(defn- pick-directories\n  \"Given a path, use it to find directories. If the path names a directory,\n  return a vector containing it. If the path ends in `/*` and the parent is a\n  directory, return a sequence of directories which are children of the parent.\n  If the path end in `/**` and the parent is a directory, returns a sequence\n  of directories searched recursively which are project (contains project.clj\n  file). Otherwise, returns nil.\"\n  [^File file]\n  (cond\n    (.isDirectory file)\n    [file]\n\n    (and (= \"*\" (.getName file)) (.isDirectory (.getParentFile file)))\n    (->> (.getParentFile file)\n         (.listFiles)\n         (filter #(.isDirectory ^File %)))\n\n    (and (= \"**\" (.getName file)) (.isDirectory (.getParentFile file)))\n    (all-projects (.getParentFile file))\n\n    :else nil))\n\n\n(defn- with-absolute-clean-targets\n  \"Returns the given project map with its clean targets updated to use absolute paths\n   to work around https://github.com/technomancy/leiningen/issues/2707\"\n  [{:keys [root clean-targets] :as project}]\n  (let [abs-target-fn (fn [target]\n                        (if (and (string? target)\n                                 (not (.isAbsolute (io/file target))))\n                          (str (io/file root target))\n                          target))\n        abs-clean-targets (with-meta (mapv abs-target-fn clean-targets) (meta clean-targets))]\n    (assoc project :clean-targets abs-clean-targets)))\n\n\n(defn- read-subproject\n  \"Reads a leiningen project definition from the given directory and returns\n  the loaded project map, or nil if the directory does not contain a valid\n  `project.clj` file.\"\n  [dir]\n  (let [project-file (io/file dir \"project.clj\")]\n    (when (.exists project-file)\n      (lein/debug \"Reading subproject definition from\" (str project-file))\n      (-> (project/read-raw (str project-file))\n          (with-absolute-clean-targets)))))\n\n\n(defn read-subprojects!\n  \"Returns a map of (condensed) project names to raw leiningen project\n  definitions for all the subprojects in the repo.\"\n  [monolith]\n  (->>\n    (get-in monolith [:monolith :project-dirs])\n    (map (partial io/file (:root monolith)))\n    (mapcat pick-directories)\n    (keep read-subproject)\n    (map (juxt dep/project-name identity))\n    (into {})\n    (debug-profile \"read-subprojects!\")))\n"
  },
  {
    "path": "src/lein_monolith/dependency.clj",
    "content": "(ns lein-monolith.dependency\n  \"Functions for working with dependency coordinates and graphs.\"\n  (:require\n    [clojure.set :as set]\n    [clojure.string :as str]\n    [lein-monolith.color :refer [colorize]]\n    [leiningen.core.main :as lein]))\n\n\n;; ## Coordinate Functions\n\n(defn condense-name\n  \"Simplifies a dependency name symbol with identical name and namespace\n  components to a symbol with just a name.\"\n  [sym]\n  (when sym\n    (if (= (namespace sym) (name sym))\n      (symbol (name sym))\n      sym)))\n\n\n(defn project-name\n  \"Extracts the (condensed) project name from a project definition map.\"\n  [project]\n  (when project\n    (condense-name (symbol (:group project) (:name project)))))\n\n\n(defn resolve-name\n  \"Given a set of valid project names, determine the match for the named\n  project. This can be used to resolve the short name (meaning, no namespace)\n  to a fully-qualified project name. Returns a resolved key from\n  `project-names`, a collection of multiple matching keys, or nil if the\n  resolution fails.\"\n  [project-names sym]\n  (let [valid-keys (set project-names)]\n    (cond\n      (valid-keys sym)\n      sym\n\n      (valid-keys (condense-name sym))\n      (condense-name sym)\n\n      (nil? (namespace sym))\n      (let [candidates (filter #(= (name %) (name sym)) valid-keys)]\n        (if (= 1 (count candidates))\n          (first candidates)\n          (seq candidates)))\n\n      :else nil)))\n\n\n(defn resolve-name!\n  \"Resolves a symbol to a single project name, or calls abort if no or multiple\n  projects match.\"\n  [project-names sym]\n  (let [result (resolve-name project-names sym)]\n    (cond\n      (nil? result)\n      (lein/abort \"Could not resolve\" sym \"to any monolith subproject!\")\n\n      (coll? result)\n      (lein/abort \"Name\" sym \"resolves to multiple monolith subprojects:\"\n                  (str/join \" \" (sort result)))\n\n      :else result)))\n\n\n(defn clean-coord\n  \"Removes the `:scope` entry from a leiningen dependency coordinate vector,\n  if it is present. Preserves any metadata on the coordinate.\"\n  [coord]\n  (into (empty coord)\n        (comp\n          (partition-all 2)\n          (remove (comp #{:scope :exclusions} first))\n          cat)\n        coord))\n\n\n(defn with-source\n  \"Attaches metadata to a dependency vector which notes the source project.\"\n  [dependency project-name]\n  (vary-meta dependency assoc :monolith/project project-name))\n\n\n(defn dep-source\n  \"Retrieves the project which pulled in the dependency from metadata on the\n  spec vector.\"\n  [dependency]\n  (:monolith/project (meta dependency)))\n\n\n;; ## Dependency Graphs\n\n(defn- collect-dependencies\n  \"Merges the project's top-level dependencies with all dependencies listed in\n  the project's profiles to ensure the project has the proper dependency closure\n  for compilation ordering.\"\n  [project]\n  (->>\n    project\n    (:profiles)\n    (vals)\n    (cons project)\n    (mapcat :dependencies)\n    (map (comp condense-name first))\n    (set)))\n\n\n(defn dependency-map\n  \"Converts a map of project names to definitions into a map of project names\n  to sets of projects that node depends on.\"\n  [projects]\n  (->>\n    (vals projects)\n    (map collect-dependencies)\n    (zipmap (keys projects))))\n\n\n(defn upstream-keys\n  \"Returns a set of the keys which are upstream of a given node in the\n  dependency map. Includes the root value itself.\"\n  [dependencies root]\n  (loop [result #{}\n         queue (conj (clojure.lang.PersistentQueue/EMPTY) root)]\n    (cond\n      ;; Nothing left to process.\n      (empty? queue) result\n\n      ;; Already seen this node.\n      (contains? result (peek queue))\n      (recur result (pop queue))\n\n      ;; Add next set of dependencies.\n      :else\n      (let [node (peek queue)\n            deps (dependencies node)]\n        (recur (conj result node)\n               (into (pop queue) (set/difference deps result)))))))\n\n\n(defn downstream-keys\n  \"Returns a set of the keys which are downstream of a given node in the\n  dependency map. Includes the root value itself.\"\n  [dependencies root]\n  (let [deps-on (fn deps-on\n                  [n]\n                  (set (keep (fn [[k deps]] (when (deps n) k))\n                             dependencies)))]\n    (loop [result #{}\n           queue (conj (clojure.lang.PersistentQueue/EMPTY) root)]\n      (cond\n        ;; Nothing left to process.\n        (empty? queue) result\n\n        ;; Already seen this node, deps are either present or already queued.\n        (contains? result (peek queue))\n        (recur result (pop queue))\n\n        ;; Add next set of dependencies.\n        :else\n        (let [node (peek queue)\n              consumers (deps-on node)]\n          (recur (conj result node)\n                 (into (pop queue) (set/difference consumers result))))))))\n\n\n(defn unique-cycles\n  \"Return a set of all unique cycles in dependency graph m.\"\n  [m]\n  {:pre [(map? m)]\n   :post [(set? %)]}\n  (let [path->cycles (fn path->cycles\n                       [path]\n                       {:pre [(seq path)]}\n                       (let [k (peek path)\n                             vs (m k)\n                             path-set (set path)]\n                         (mapcat (fn [v]\n                                   (if (path-set v)\n                                     ;; found the cycle\n                                     [(into []\n                                            ;; drop non-cyclic prefix\n                                            (drop-while (complement #{v}))\n                                            (conj path v))]\n                                     (path->cycles\n                                       (conj path v))))\n                                 vs)))\n        all-cycles (mapcat #(path->cycles [%]) (keys m))\n        ;; remove duplicate cycles (that involve the same deps) -- like\n        ;;   (into #{}\n        ;;         (map (comp first val))\n        ;;         (group-by set all-cycles))\n        ;; but in a single pass.\n        [cycles-vecs _] (reduce (fn [[cycles-vecs cycle-sets] c]\n                                  (let [cset (set c)]\n                                    (if (cycle-sets cset)\n                                      [cycles-vecs cycle-sets]\n                                      [(conj cycles-vecs c)\n                                       (conj cycle-sets cset)])))\n                                [#{} #{}]\n                                all-cycles)]\n    cycles-vecs))\n\n\n(defn pretty-cycle\n  \"Returns a pretty-printed string representation of cycle c.\n\n  eg. (println (pretty-cycle [1 2 3 1])) =>\n      + 1\n      ^ + 2\n      |  + 3\n      |_/\"\n  [c]\n  {:pre [(vector? c)\n         (= (first c) (peek c))]}\n  (if (= 2 (count c))\n    (str/join \\newline\n              [(str \"+ \" (pr-str (peek c)))\n               \"^\\\\\"\n               \"|_|\"])\n    (str/join (map-indexed (fn [indent el]\n                             (if (= indent (dec (count c)))\n                               ;; draw:\n                               ;; |___/\n                               (str\n                                 \\|\n                                 (str/join (repeat (max 1 (- indent 2)) \\_))\n                                 \\/)\n                               (str\n                                 (case (int indent)\n                                   0 \"\"\n                                   1 \\^\n                                   \\|)\n                                 (str/join (repeat indent \\space))\n                                 \"+ \" (pr-str el) \"\\n\")))\n                           c))))\n\n\n(defn topological-sort\n  \"Returns a sequence of the keys in the map `m`, ordered such that no key `k1`\n  appearing before `k2` satisfies `(contains? (upstream-keys m k1) k2)`. In\n  other words, earlier keys do not transitively depend on any later keys.\"\n  ([m]\n   (when (seq m)\n     ;; Note that 'roots' here are keys which no other keys depend on, hence\n     ;; should appear *later* in the sequence.\n     (let [roots (apply set/difference (set (keys m)) (map set (vals m)))]\n       (when (empty? roots)\n         (let [cs (->> m\n                       unique-cycles\n                       (sort-by count))]\n           (assert (seq cs) \"Found cycle but failed to reproduce\")\n           (throw (ex-info (str \"Dependency cycle\"\n                                (when (next cs) \"s\")\n                                \" detected!\\n\\n\"\n                                (str/join \"\\n\\n\" (map pretty-cycle cs)))\n                           {:cycles cs}))))\n       (concat (topological-sort (apply dissoc m roots))\n               (sort roots)))))\n  ([m ks]\n   (filter (set ks) (topological-sort m))))\n\n\n;; ## Dependency Resolution\n\n(defn sourced-dependencies\n  \"Given a project map, returns a sequence of dependency coordinates with\n  metadata tracking the source.\"\n  [project]\n  (let [pn (project-name project)]\n    (map #(with-source % pn) (:dependencies project))))\n\n\n(defn lint-dependency\n  \"Given a dependency name and a collection of specs for that dependency, warn\n  if there are multiple distinct dep coordinates.\"\n  [dep-name specs]\n  (let [specs (mapv clean-coord specs)\n        projects-for-specs (reduce (fn [m d]\n                                     (update m d (fnil conj []) (dep-source d)))\n                                   {}\n                                   specs)]\n    (when (not= 1 (count (distinct specs)))\n      (lein/warn (colorize :red (format \"WARN: Multiple dependency specs found for %s in %d projects\"\n                                        (condense-name dep-name)\n                                        (count (distinct (map dep-source specs))))))\n      (doseq [[spec projects] projects-for-specs]\n        (lein/warn (format \"%-50s from %s\"\n                           (pr-str spec)\n                           (str/join \" \" (sort projects)))))\n      (lein/warn \"\"))))\n"
  },
  {
    "path": "src/lein_monolith/plugin.clj",
    "content": "(ns lein-monolith.plugin\n  \"This namespace runs inside of Leiningen on all projects and handles profile\n  creation for `with-all` and `inherit` functionality.\"\n  (:require\n    [lein-monolith.config :as config]\n    [lein-monolith.dependency :as dep]\n    [leiningen.core.main :as lein]\n    [leiningen.core.project :as project]))\n\n\n;; ## Profile Generation\n\n(def profile-config\n  \"Configuration for inherited profiles. Structured as a vector of pairs to\n  maintain ordering. The ordering is significant as the info command consumes\n  this configuration directly, and providing deterministic output ordering is\n  desirable.\"\n  [[:monolith/inherited\n    {:inherit-key :inherit\n     :subproject-key :monolith/inherit}]\n\n   [:monolith/inherited-raw\n    {:raw? true\n     :inherit-key :inherit-raw\n     :subproject-key :monolith/inherit-raw}]\n\n   [:monolith/leaky\n    {:leaky? true\n     :inherit-key :inherit-leaky\n     :subproject-key :monolith/inherit-leaky}]\n\n   [:monolith/leaky-raw\n    {:leaky? true\n     :raw? true\n     :inherit-key :inherit-leaky-raw\n     :subproject-key :monolith/inherit-leaky-raw}]])\n\n\n(defn- subproject-dependencies\n  \"Given a map of internal projects, return a vector of dependency coordinates\n  for the subprojects.\"\n  [subprojects]\n  (mapv #(vector (key %) (:version (val %))) subprojects))\n\n\n(defn- maybe-mark-leaky\n  \"Add ^:leaky metadata to a profile if it is of the leaky type.\"\n  [profile {:keys [leaky?]}]\n  (if leaky?\n    (vary-meta profile assoc :leaky true)\n    profile))\n\n\n(defn- choose-inheritance-source\n  \"Choose either the initialized monolith or its raw representation for use when\n  building an inherited profile.\"\n  [monolith {:keys [raw?]}]\n  (if raw?\n    (:monolith/raw (meta monolith))\n    monolith))\n\n\n(defn- select-inherited-properties\n  \"Constructs a profile map containing the inherited properties from a parent\n  project map.\"\n  [monolith base-properties subproject subproject-key]\n  (let [default (boolean (:monolith/inherit subproject))\n        setting (subproject-key subproject default)]\n    (cond\n      ;; Don't inherit anything\n      (not setting)\n      nil\n\n      ;; Inherit the base properties specified in the parent.\n      (true? setting)\n      (select-keys monolith base-properties)\n\n      ;; Provide additional properties to inherit, or replace if metadata is set.\n      (vector? setting)\n      (select-keys monolith\n                   (if (:replace (meta setting))\n                     setting\n                     (distinct (concat base-properties setting))))\n\n      :else\n      (throw (ex-info (str \"Unknown value type for monolith inherit setting: \"\n                           (pr-str setting))\n                      {:inherit setting\n                       :subproject-key subproject-key})))))\n\n\n(defn- inherited-profile\n  \"Constructs a profile map containing the inherited properties from a parent\n  project map.\"\n  [monolith subproject {:keys [inherit-key subproject-key]}]\n  (when-let [base-properties (get-in monolith [:monolith inherit-key])]\n    (when-let [profile (select-inherited-properties monolith base-properties subproject subproject-key)]\n      (when (contains? profile :profiles)\n        (lein/warn \"WARN: nested profiles cannot be inherited; ignoring :profiles in monolith\" inherit-key))\n      (dissoc profile :profiles))))\n\n\n(defn build-inherited-profiles\n  \"Returns a vector of map entries from profile keys to inherited profile maps.\n   We use a vector here instead of a map to preserve profile ordering.\"\n  [monolith subproject]\n  (reduce\n    (fn [acc [key config]]\n      (let [profile (some-> (choose-inheritance-source monolith config)\n                            (inherited-profile subproject config)\n                            (maybe-mark-leaky config))]\n        (if profile\n          (conj acc [key profile])\n          acc)))\n    []\n    profile-config))\n\n\n(defn build-dependency-profiles\n  \"Constructs a vector with a profile map entries containing managed dependencies from the subproject's chosen dependency set.\n   Returns nil if the subproject does not use a dependency set.\"\n  [monolith subproject]\n  (when-let [dependency-set (:monolith/dependency-set subproject)]\n    (let [dependencies (or (get-in monolith [:monolith :dependency-sets dependency-set])\n                           (lein/abort (format \"Unknown dependency set %s used in project %s\" dependency-set (:name subproject))))]\n      [[:monolith/dependency-set\n        ^:leaky {:managed-dependencies (vary-meta dependencies assoc :replace true)}]])))\n\n\n(defn build-profiles\n  \"Constructs a vector of profile keys to inherited profile maps and the dependency set profile map.\n   We use a vector here instead of a map to preserve profile ordering.\"\n  [monolith subproject]\n  ;; The dependency set profile should be the first profile, so that its managed dependencies\n  ;; take precedence.\n  (vec (concat\n         (build-dependency-profiles monolith subproject)\n         (build-inherited-profiles monolith subproject))))\n\n\n;; ## Profile Utilities\n\n(defn profile-active?\n  \"Check whether the given profile key is in the set of active profiles on the\n  given project.\"\n  [project profile-key]\n  (contains? (set (:active-profiles (meta project))) profile-key))\n\n\n(defn add-profile\n  \"Adds the monolith profile to the given project if it's not already present.\"\n  [project profile-key profile]\n  (if (= profile (get-in project [:profiles profile-key]))\n    project\n    (do (lein/debug \"Adding\" profile-key \"profile to project\" (dep/project-name project))\n        (project/add-profiles project {profile-key profile}))))\n\n\n(defn activate-profile\n  \"Activates the monolith profile in the project if it's not already active.\"\n  [project profile-key]\n  (if (profile-active? project profile-key)\n    project\n    (do (lein/debug \"Merging\" profile-key \"profile into project\" (dep/project-name project))\n        (project/merge-profiles project [profile-key]))))\n\n\n(defn add-active-profile\n  \"Combines the effects of `add-profile` and `activate-profile`.\"\n  [project profile-key profile]\n  (-> project\n      (add-profile profile-key profile)\n      (activate-profile profile-key)))\n\n\n;; ## Plugin Middleware\n\n(defn- add-profiles\n  \"Adds profiles to the project. Profiles should be passed in as a vector to ensure profiles are\n   added in the right order.\"\n  [project profiles]\n  (if (empty? profiles)\n    project\n    (-> project\n        (project/add-profiles (into {} profiles))\n        (project/merge-profiles (mapv first profiles)))))\n\n\n(defn middleware\n  \"Handles inherited properties in monolith subprojects by looking for the\n  `:monolith/inherit*` keys, or sets the managed dependencies if :monolith/dependency-set is set.\"\n  ([project]\n   (middleware project nil))\n  ([project monolith]\n   (if (:monolith/active (meta project))\n     ;; Already activated monolith subproject, don't activate.\n     project\n     ;; Monolith subproject has not yet been activated, potentially add profiles.\n     (let [monolith (or monolith (config/find-monolith project))\n           profiles (build-profiles monolith project)]\n       (-> project\n           (vary-meta assoc :monolith/active true)\n           (add-profiles profiles))))))\n\n\n(defn add-middleware\n  \"Update the given project to include the plugin middleware. Appends the\n  middleware symbol if it is not already present.\"\n  [subproject]\n  (let [mw-sym 'lein-monolith.plugin/middleware]\n    (if (some #{mw-sym} (:middleware subproject))\n      subproject\n      (update subproject :middleware (fnil conj []) mw-sym))))\n\n\n;; ## Merged Profiles\n\n(def ^:private path-keys\n  \"Project map keys for (re)source and test paths.\"\n  #{:resource-paths :source-paths :test-paths})\n\n\n(defn- add-profile-paths\n  \"Update a profile paths entry by adding the paths from the given project.\n  Returns the updated profile.\"\n  [project profile k]\n  (update profile k (fn combine-colls\n                      [coll]\n                      (-> coll\n                          set\n                          (into (get project k))\n                          (vary-meta assoc :replace true)))))\n\n\n(defn merged-profile\n  \"Constructs a profile map containing merged (re)source and test paths.\"\n  [monolith subprojects]\n  (let [profile\n        (reduce-kv\n          (fn [profile _project-name subproject]\n            (let [with-profiles (middleware subproject monolith)\n                  project (project/absolutize-paths with-profiles)]\n              (reduce (partial add-profile-paths project)\n                      profile\n                      path-keys)))\n          (select-keys monolith path-keys)\n          subprojects)]\n    (as-> profile v\n          (reduce (fn sort-paths [acc k] (update acc k sort)) v path-keys)\n          (assoc v :dependencies (subproject-dependencies subprojects)))))\n"
  },
  {
    "path": "src/lein_monolith/target.clj",
    "content": "(ns lein-monolith.target\n  \"Functions for constructing and operating on dependency closures.\"\n  (:require\n    [clojure.set :as set]\n    [clojure.string :as str]\n    [lein-monolith.dependency :as dep]\n    [leiningen.core.main :as lein]))\n\n\n(def selection-opts\n  {:in* 1\n   :upstream-of* 1\n   :downstream-of* 1\n   :skip* 1\n   :select* 1})\n\n\n(defn- resolve-projects\n  \"Returns a set of project names that have been resolved from the given\n  sequence, which may consist of multiple comma-separated lists. Ignores names\n  which do not map to a project.\"\n  [subprojects name-args]\n  (some->>\n    (seq name-args)\n    (mapcat #(str/split % #\"\\s*,\\s*\"))\n    (map read-string)\n    (map (partial dep/resolve-name! (keys subprojects)))\n    (set)))\n\n\n(defn- resolve-selector\n  \"Looks up the subproject selector from the configuration map. Aborts if a\n  selector is specified but does not exist.\"\n  [monolith selector-key]\n  (when selector-key\n    (let [selectors (get-in monolith [:monolith :project-selectors])\n          selector (get selectors selector-key)]\n      (when-not selector\n        (lein/abort (format \"Project selector %s is not configured in %s\"\n                            selector-key (keys selectors))))\n      (eval selector))))\n\n\n(defn- combine-selectors\n  \"Returns a selection function to filter projects based on the `:select`\n  options passed. Multiple selection functions apply cumulative layers of\n  filtering, meaning a project must pass _every_ selector to be included.\n  Returns nil if no selectors were specified.\"\n  [monolith select-args]\n  (some->>\n    (seq select-args)\n    (map read-string)\n    (keep (partial resolve-selector monolith))\n    (apply every-pred)))\n\n\n(defn- filter-selected\n  \"Applies a project-selector function to the topologically-sorted set of\n  projects to produce a final sequence of projects.\"\n  [subprojects selector targets]\n  (->>\n    (sort targets)\n    (map-indexed (fn [i p] [p (assoc (subprojects p) :monolith/index i)]))\n    (filter (comp selector second))\n    (map first)))\n\n\n(defn select\n  \"Returns a set of canonical project names selected by the given options.\"\n  [monolith subprojects opts]\n  (let [dependencies (dep/dependency-map subprojects)\n        skippable (resolve-projects subprojects (:skip opts))\n        selector (combine-selectors monolith (:select opts))]\n    (->\n      ;; Start with explicitly-specified 'in' targets.\n      (resolve-projects subprojects (:in opts))\n      (as-> targets\n        ;; Merge all targeted upstream dependencies.\n        (->> (:upstream-of opts)\n             (resolve-projects subprojects)\n             (map (partial dep/upstream-keys dependencies))\n             (reduce set/union targets))\n        ;; Merge all targeted downstream dependencies.\n        (->> (:downstream-of opts)\n             (resolve-projects subprojects)\n             (map (partial dep/downstream-keys dependencies))\n             (reduce set/union targets))\n        ;; If target set empty, replace with full set.\n        (if (empty? targets)\n          (set (keys subprojects))\n          targets))\n      (cond->>\n        ;; Exclude all 'skip' targets.\n        skippable (remove skippable)\n        ;; Filter using the selector, if any.\n        selector (filter-selected subprojects selector))\n      (set))))\n"
  },
  {
    "path": "src/lein_monolith/task/checkouts.clj",
    "content": "(ns lein-monolith.task.checkouts\n  (:require\n    [clojure.java.io :as io]\n    [clojure.string :as str]\n    [lein-monolith.dependency :as dep]\n    [lein-monolith.task.util :as u]\n    [leiningen.core.main :as lein])\n  (:import\n    java.io.File\n    (java.nio.file\n      Files\n      LinkOption\n      Path)))\n\n\n(defn- create-symlink!\n  \"Creates a link from the given source path to the given target.\"\n  [source target]\n  (Files/createSymbolicLink\n    source target\n    (make-array java.nio.file.attribute.FileAttribute 0)))\n\n\n(defn- resolve-symlink\n  \"Read a symlink at the given path and return the canonical path to its\n  target.\"\n  [^Path link]\n  (let [target (Files/readSymbolicLink link)]\n    (if (.isAbsolute target)\n      (.toRealPath target (into-array LinkOption []))\n      (-> (.getParent link)\n          (.resolve target)\n          (.toRealPath (into-array LinkOption []))))))\n\n\n(defn- link-checkout!\n  \"Creates a checkout dependency link to the given subproject.\"\n  [^File checkouts-dir subproject force?]\n  (let [dep-root (io/file (:root subproject))\n        dep-name (dep/project-name subproject)\n        link-name (if (namespace dep-name)\n                    (str (namespace dep-name) \"~\" (name dep-name))\n                    (name dep-name))\n        link-path (.toPath (io/file checkouts-dir link-name))\n        target-path (.relativize (.toPath checkouts-dir) (.toPath dep-root))]\n    (if (Files/exists link-path (into-array LinkOption [LinkOption/NOFOLLOW_LINKS]))\n      ;; Link file exists.\n      (let [actual-target (Files/readSymbolicLink link-path)]\n        (if (and (Files/isSymbolicLink link-path)\n                 (= target-path actual-target))\n          ;; Link exists and points to target already.\n          (lein/debug \"Link for\" dep-name \"is correct\")\n          ;; Link exists but points somewhere else.\n          (if force?\n            ;; Recreate link since force is set.\n            (do (lein/warn \"Relinking\" dep-name \"from\"\n                           (str actual-target) \"to\" (str target-path))\n                (Files/delete link-path)\n                (create-symlink! link-path target-path))\n            ;; Otherwise print a warning.\n            (lein/warn \"WARN:\" dep-name \"links to\" (str actual-target)\n                       \"instead of\" (str target-path)))))\n      ;; Link does not exist, so create it.\n      (do (lein/info \"Linking\" dep-name \"to\" (str target-path))\n          (create-symlink! link-path target-path)))))\n\n\n(defn link\n  \"Create symlinks in the checkouts directory pointing to all internal\n  dependencies in the current project.\"\n  [project opts project-names]\n  (let [[_ subprojects] (u/load-monolith! project)\n        dep-map (dep/dependency-map subprojects)\n        selected-names (into #{}\n                             (map (partial dep/resolve-name! (keys dep-map)))\n                             project-names)\n        projects-to-link (->\n                           (map (comp dep/condense-name first)\n                                (:dependencies project))\n                           (cond->>\n                             (or (:deep opts) (seq project-names))\n                             (mapcat (partial dep/upstream-keys dep-map))\n\n                             (seq selected-names)\n                             (filter selected-names))\n                           (->>\n                             (distinct)\n                             (keep subprojects)))\n        checkouts-dir (io/file (:root project) \"checkouts\")]\n    (when (empty? projects-to-link)\n      (lein/abort (str \"Couldn't find any projects to link matching: \"\n                       (str/join \" \" project-names))))\n    ;; Create checkouts directory if needed.\n    (when-not (.exists checkouts-dir)\n      (lein/debug \"Creating checkout directory\" (str checkouts-dir))\n      (.mkdir checkouts-dir))\n    ;; Check each dependency for internal projects.\n    (doseq [subproject projects-to-link]\n      (link-checkout! checkouts-dir subproject (:force opts)))))\n\n\n(defn unlink\n  \"Remove the checkouts directory from a project.\"\n  [project opts project-names]\n  (when-let [checkouts-dir (some-> (:root project) (io/file \"checkouts\"))]\n    (when (.exists checkouts-dir)\n      (lein/debug \"Unlinking checkouts in\" (str checkouts-dir))\n      (let [[_ subprojects] (u/load-monolith! project)\n            root->subproject (into {}\n                                   (map (juxt (comp str :root val) key))\n                                   subprojects)\n            selected-names (into #{}\n                                 (map (partial dep/resolve-name! (keys subprojects)))\n                                 project-names)]\n        ;; For each file in the checkouts directory.\n        (doseq [link (.listFiles checkouts-dir)\n                :let [link-path (.toPath ^File link)]]\n          (when (or (:all opts)\n                    ;; Check that the file is a symlink and points to a known\n                    ;; subproject that we want to remove.\n                    (when (Files/isSymbolicLink link-path)\n                      (let [link-target (resolve-symlink link-path)\n                            target-name (root->subproject (str link-target))]\n                        (and target-name\n                             (or (empty? selected-names)\n                                 (contains? selected-names target-name))))))\n            (lein/debug \"Removing checkout link\" (str link))\n            (Files/delete link-path))))\n      ;; If the directory is empty, clean up.\n      (when (empty? (.listFiles checkouts-dir))\n        (.delete checkouts-dir)))))\n"
  },
  {
    "path": "src/lein_monolith/task/each.clj",
    "content": "(ns lein-monolith.task.each\n  (:require\n    [clojure.java.io :as io]\n    [clojure.stacktrace :as cst]\n    [clojure.string :as str]\n    [lein-monolith.color :refer [colorize]]\n    [lein-monolith.config :as config]\n    [lein-monolith.dependency :as dep]\n    [lein-monolith.plugin :as plugin]\n    [lein-monolith.target :as target]\n    [lein-monolith.task.fingerprint :as fingerprint]\n    [lein-monolith.task.util :as u]\n    [leiningen.core.eval :as eval]\n    [leiningen.core.main :as lein]\n    [leiningen.core.project :as project]\n    [leiningen.core.utils :as utils :refer [rebind-io!]]\n    [leiningen.do :as lein-do]\n    [manifold.deferred :as d]\n    [manifold.executor :as executor])\n  (:import\n    (com.hypirion.io\n      ClosingPipe\n      Pipe\n      RevivableInputStream)\n    (java.io\n      ByteArrayOutputStream\n      OutputStream)\n    java.time.Instant))\n\n\n;; ## Task Options\n\n(def task-opts\n  (merge\n    target/selection-opts\n    {:parallel 1\n     :endure 0\n     :report 0\n     :silent 0\n     :output 1\n     :upstream 0\n     :downstream 0\n     :start 1\n     :changed 1\n     :refresh 1}))\n\n\n(defn- opts->args\n  \"Converts a set of options back into the arguments that created them. Returns\n  a sequence of keywords and strings.\"\n  [opts]\n  (concat\n    (when-let [threads (:parallel opts)]\n      [:parallel threads])\n    (when (:endure opts)\n      [:endure])\n    (when (:report opts)\n      [:report])\n    (when (:silent opts)\n      [:silent])\n    (when-let [out-dir (:output opts)]\n      [:output out-dir])\n    (when-let [in (seq (:in opts))]\n      [:in (str/join \",\" in)])\n    (when (:upstream opts)\n      [:upstream])\n    (when-let [uof (seq (:upstream-of opts))]\n      [:upstream-of (str/join \",\" uof)])\n    (when (:downstream opts)\n      [:downstream])\n    (when-let [dof (seq (:downstream-of opts))]\n      [:downstream-of (str/join \",\" dof)])\n    (when-let [selectors (seq (:select opts))]\n      (mapcat (partial vector :select) selectors))\n    (when-let [skips (seq (:skip opts))]\n      [:skip (str/join \",\" skips)])\n    (if-let [refresh (:refresh opts)]\n      [:refresh refresh]\n      (when-let [changed (:changed opts)]\n        [:changed changed]))\n    (when-let [start (:start opts)]\n      [:start start])))\n\n\n;; ## Project Initialization\n\n(defn- init-project\n  \"Initialize the given subproject to prepare to run a task in it.\"\n  [subproject]\n  (config/debug-profile \"init-subproject\"\n    (project/init-project\n      (plugin/add-middleware subproject))))\n\n\n(defn- resolve-tasks\n  \"Perform an initial resolution of the task to prevent metadata-related\n  arglist errors when namespaces are loaded in parallel.\"\n  [project task+args]\n  (let [[task args] (lein/task-args task+args project)]\n    (lein/resolve-task task)\n    ;; Some tasks pull in other tasks, so also resolve them.\n    (case task\n      \"do\"\n      (doseq [subtask+args (lein-do/group-args args)]\n        (resolve-tasks project subtask+args))\n\n      \"update-in\"\n      (let [subtask+args (rest (drop-while #(not= \"--\" %) args))]\n        (resolve-tasks project subtask+args))\n\n      \"with-profile\"\n      (let [subtask+args (rest args)]\n        (resolve-tasks project subtask+args))\n\n      ;; default no-op\n      nil)))\n\n\n;; ## Output Handling\n\n(def ^:private output-lock\n  \"An object to lock on to ensure that project output is not interleaved when\n  running in silent mode.\"\n  (Object.))\n\n\n(def ^:private ^:dynamic *task-capture-output*\n  \"If bound, write task output to this stream *instead* of the standard output\n  and error streams.\"\n  nil)\n\n\n(def ^:private ^:dynamic *task-file-output*\n  \"If bound, copy task output to this stream to record it to a log file.\"\n  nil)\n\n\n(defn- tee-output-stream\n  \"Constructs a proxy of an OutputStream that will write a copy of the bytes\n  given to both A and B.\"\n  ^OutputStream\n  [^OutputStream out-a ^OutputStream out-b]\n  (proxy [OutputStream] []\n\n    (write\n      ([value]\n       (locking out-b\n         (if (integer? value)\n           (do (.write out-a (int value))\n               (.write out-b (int value)))\n           (do (.write out-a ^bytes value)\n               (.write out-b ^bytes value)))))\n      ([^bytes byte-arr off len]\n       (locking out-b\n         (.write out-a byte-arr off len)\n         (.write out-b byte-arr off len))))\n\n    (flush\n      []\n      (.flush out-a)\n      (.flush out-b))\n\n    (close\n      []\n      ;; no-op\n      nil)))\n\n\n(defn- run-with-output\n  \"A version of `leiningen.core.eval/sh` that streams in/out/err, teeing output\n  to the given file.\"\n  [& cmd]\n  (when eval/*pump-in*\n    (rebind-io!))\n  (let [cmd (into-array String cmd)\n        env (into-array String (@#'eval/overridden-env eval/*env*))\n        proc (.exec (Runtime/getRuntime)\n                    ^{:tag \"[Ljava.lang.String;\"} cmd\n                    ^{:tag \"[Ljava.lang.String;\"} env\n                    (io/file eval/*dir*))]\n    (.addShutdownHook (Runtime/getRuntime)\n                      (Thread. (fn [] (.destroy proc))))\n    (with-open [out (.getInputStream proc)\n                err (.getErrorStream proc)\n                in (.getOutputStream proc)]\n      (let [out-dest (cond-> (or *task-capture-output* System/out)\n                       *task-file-output*\n                       (tee-output-stream *task-file-output*))\n            err-dest (cond-> (or *task-capture-output* System/err)\n                       *task-file-output*\n                       (tee-output-stream *task-file-output*))\n            pump-out (doto (Pipe. out ^OutputStream out-dest)\n                       (.start))\n            pump-err (doto (Pipe. err ^OutputStream err-dest)\n                       (.start))\n            pump-in (ClosingPipe. System/in in)]\n        (when eval/*pump-in* (.start pump-in))\n        (.join pump-out)\n        (.join pump-err)\n        (let [exit-value (.waitFor proc)]\n          (when eval/*pump-in*\n            (.kill ^RevivableInputStream System/in)\n            (.join pump-in)\n            (.resurrect ^RevivableInputStream System/in))\n          (when *task-file-output*\n            (.flush ^OutputStream *task-file-output*))\n          exit-value)))))\n\n\n(defn- apply-with-output\n  \"Applies the function to the given subproject, writing the task output to a\n  file in the given directory.\"\n  [out-dir f subproject task]\n  (let [out-file (io/file out-dir (:group subproject) (str (:name subproject) \".txt\"))\n        elapsed (u/stopwatch)]\n    (io/make-parents out-file)\n    (with-open [file-output-stream (io/output-stream out-file :append true)]\n      ;; Write task header\n      (.write file-output-stream\n              (.getBytes (format \"[%s] Applying task to %s/%s: %s\\n\\n\"\n                                 (Instant/now)\n                                 (:group subproject)\n                                 (:name subproject)\n                                 (str/join \" \" task))))\n      (try\n        ;; Run task with output capturing.\n        (binding [*task-file-output* file-output-stream]\n          (f subproject task))\n        (catch Exception ex\n          (.write file-output-stream\n                  (.getBytes (format \"\\nERROR: %s\\n%s\"\n                                     (ex-message ex)\n                                     (with-out-str\n                                       (cst/print-cause-trace ex)))))\n          (throw ex))\n        (finally\n          ;; Write task footer\n          (.write file-output-stream\n                  (.getBytes (format \"\\n[%s] Elapsed: %s\\n\"\n                                     (Instant/now)\n                                     (u/human-duration @elapsed)))))))))\n\n\n;; ## Task Execution\n\n(defn- thread-safe-require-resolve\n  \"Replacement for `leiningen.core.utils/require-resolve` that is safe to\n  execute in multiple threads.\"\n  ([ns sym]\n   (thread-safe-require-resolve (symbol ns sym)))\n  ([sym]\n   (if (qualified-symbol? sym)\n     (try\n       (requiring-resolve sym)\n       (catch Exception _\n         nil))\n     (resolve sym))))\n\n\n(defn- apply-subproject-task\n  \"Applies the task to the given subproject.\"\n  [subproject task]\n  (binding [lein/*exit-process?* false\n            eval/*dir* (:root subproject)]\n    (with-redefs [utils/require-resolve thread-safe-require-resolve]\n      (let [initialized (init-project subproject)]\n        (config/debug-profile \"apply-task\"\n          (lein/resolve-and-apply initialized task))))))\n\n\n(defn- run-task!\n  \"Runs the given task, returning a map of information about the run.\"\n  [ctx target]\n  ;; Try to reclaim some memory before running the task.\n  (System/gc)\n  (let [subproject (get-in ctx [:subprojects target])\n        opts (:opts ctx)\n        marker (:changed opts)\n        fprints (:fingerprints ctx)\n        elapsed (u/stopwatch)\n        task-output (when (:silent opts)\n                      (ByteArrayOutputStream.))]\n    (try\n      (lein/info (format \"\\nApplying to %s%s\"\n                         (colorize [:bold :yellow] target)\n                         (if marker\n                           (str \" (\" (fingerprint/explain-str fprints marker target) \")\")\n                           \"\")))\n      ;; Bind appropriate output options and apply the task.\n      (binding [*task-capture-output* task-output]\n        (if-let [out-dir (get-in ctx [:opts :output])]\n          (apply-with-output out-dir apply-subproject-task subproject (:task ctx))\n          (apply-subproject-task subproject (:task ctx))))\n      ;; Save updated fingerprint if refreshing.\n      (when (:refresh opts)\n        (fingerprint/save! fprints marker target)\n        (lein/info (format \"Saved %s fingerprint for %s\"\n                           (colorize :bold marker)\n                           (colorize [:bold :yellow] target))))\n      ;; Return successful task result.\n      {:name target\n       :elapsed @elapsed\n       :success true}\n      (catch Exception ex\n        ;; When silent, grab output lock and print task output.\n        (when (:silent opts)\n          (locking output-lock\n            (print (str task-output))\n            (flush)))\n        ;; Print convenience resume tip for user.\n        (when-not (or (:parallel opts) (:endure opts))\n          (let [resume-args (into\n                              [\"lein\" \"monolith\" \"each\"]\n                              (map u/shell-escape)\n                              (concat\n                                (opts->args (dissoc opts :start))\n                                [:start target]\n                                (:task ctx)))]\n            (lein/warn (format \"\\n%s %s\\n\"\n                               (colorize [:bold :red] \"Resume with:\")\n                               (str/join \" \" resume-args)))))\n        ;; Fail or continue depending on whether endure is enabled.\n        (if (:endure opts)\n          (lein/warn (format \"\\n%s: %s\\n%s\"\n                             (colorize [:bold :red] \"ERROR\")\n                             (ex-message ex)\n                             (with-out-str\n                               (cst/print-cause-trace ex))))\n          (throw ex))\n        {:name target\n         :elapsed @elapsed\n         :success false\n         :error ex})\n      (finally\n        (lein/info (format \"Completed %s (%s/%s) in %s\"\n                           (colorize [:bold :yellow] target)\n                           (colorize :cyan (swap! (:completions ctx) inc))\n                           (colorize :cyan (:num-targets ctx))\n                           (colorize [:bold :cyan] (u/human-duration @elapsed))))))))\n\n\n(defn- run-linear!\n  \"Runs the task for each target in a linear (single-threaded) fashion. Returns\n  a vector of result maps in the order the tasks were executed.\"\n  [ctx targets]\n  (mapv (comp (partial run-task! ctx) second) targets))\n\n\n(defn- run-parallel!\n  \"Runs the tasks for targets in multiple worker threads, chained by dependency\n  order. Returns a vector of result maps in the order the tasks finished executing.\"\n  [ctx threads targets]\n  (let [deps (partial dep/upstream-keys (dep/dependency-map (:subprojects ctx)))\n        thread-pool (executor/fixed-thread-executor threads {:initial-thread-count threads})]\n    (resolve-tasks (:monolith ctx) (:task ctx))\n    (->\n      (reduce\n        (fn future-builder\n          [computations [_ target]]\n          (let [upstream-futures (keep computations (deps target))\n                task-runner (fn task-runner\n                              [_]\n                              (d/future-with thread-pool\n                                (lein/debug \"Starting project\" target)\n                                (run-task! ctx target)))\n                task-future (if (seq upstream-futures)\n                              (d/chain (apply d/zip upstream-futures) task-runner)\n                              (task-runner nil))]\n            (assoc computations target task-future)))\n        {}\n        targets)\n      (as-> computations\n        (mapv (comp deref computations second) targets)))))\n\n\n(defn- run-all*\n  \"Run all tasks, using the `:parallel` option to determine whether to run them\n  serially or concurrently.\"\n  [ctx targets]\n  (if-let [threads (get-in ctx [:opts :parallel])]\n    (run-parallel! ctx (Integer/parseInt threads) targets)\n    (run-linear! ctx targets)))\n\n\n(defn- run-all!\n  \"Run all tasks, using the `:parallel`, `:silent`, and `:output` options to\n  determine behavior.\"\n  [ctx targets]\n  (if (or (get-in ctx [:opts :silent])\n          (get-in ctx [:opts :output]))\n    ;; NOTE: this is done here rather than inside each task so that tasks\n    ;; starting across threads don't have a chance to see the `sh` var between\n    ;; rebindings.\n    (with-redefs [leiningen.core.eval/sh run-with-output]\n      (run-all* ctx targets))\n    (run-all* ctx targets)))\n\n\n;; ## Each Task\n\n(defn- select-projects\n  \"Returns a vector of pairs of index numbers and symbols naming the selected\n  subprojects.\"\n  [monolith subprojects fprints opts]\n  (let [dependencies (dep/dependency-map subprojects)\n        targets (target/select monolith subprojects opts)\n        start (when-let [start (:start opts)]\n                (dep/resolve-name! (keys subprojects) (read-string start)))\n        marker (:changed opts)]\n    (->\n      ;; Sort project names by dependency order.\n      (dep/topological-sort dependencies targets)\n      (cond->>\n        ;; Skip projects until the starting project, if provided.\n        start\n        (drop-while (partial not= start))\n        ;; Skip projects whose fingerprint hasn't changed.\n        marker\n        (filter (partial fingerprint/changed? fprints marker)))\n      ;; Pair names up with an index [[i project-sym] ...]\n      (->>\n        (map-indexed vector)))))\n\n\n(defn- print-report\n  \"Reports information about the tasks given a results map.\"\n  [results elapsed]\n  (let [task-time (reduce + (keep :elapsed results))\n        speedup (/ task-time elapsed)]\n    (lein/info (format \"\\n%s  %11s\"\n                       (colorize [:bold :cyan] \"Run time:\")\n                       (u/human-duration elapsed)))\n    (lein/info (format \"%s %11s\"\n                       (colorize [:bold :cyan] \"Task time:\")\n                       (u/human-duration task-time)))\n    (lein/info (format \"%s   %11.1f\"\n                       (colorize [:bold :cyan] \"Speedup:\")\n                       speedup))\n    (lein/info (->> results\n                    (sort-by :elapsed)\n                    (reverse)\n                    (take 8)\n                    (map #(format \"%-45s %s %11s\"\n                                  (colorize [:bold :yellow] (:name %))\n                                  (if (:success %) \" \" \"!\")\n                                  (u/human-duration (:elapsed %))))\n                    (str/join \"\\n\")\n                    (str \\newline\n                         (colorize [:bold :cyan] \"Slowest projects:\")\n                         \\newline)))))\n\n\n(defn run-tasks\n  \"Iterate over each subproject in the monolith and apply the given task.\"\n  [project opts task]\n  (let [[monolith subprojects] (u/load-monolith! project)\n        fprints (fingerprint/context monolith subprojects)\n        opts (if-let [marker (:refresh opts)]\n               (assoc opts :changed marker)\n               opts)\n        targets (select-projects\n                  monolith subprojects fprints\n                  (u/globalize-opts project opts))]\n    (if (seq targets)\n      (lein/info \"Applying\"\n                 (colorize [:bold :cyan] (str/join \" \" task))\n                 \"to\" (colorize :cyan (count targets))\n                 \"subprojects...\")\n      (lein/info \"Target selection matched zero subprojects; nothing to do\"))\n    (when (seq targets)\n      (let [elapsed (u/stopwatch)\n            results (run-all!\n                      {:monolith monolith\n                       :subprojects subprojects\n                       :fingerprints fprints\n                       :completions (atom (ffirst targets))\n                       :num-targets (inc (or (first (last targets)) -1))\n                       :task task\n                       :opts opts}\n                      targets)]\n        (when (:report opts)\n          (print-report results @elapsed))\n        (if-let [failures (seq (map :name (remove :success results)))]\n          (lein/abort (format \"\\n%s: Applied %s to %s projects in %s with %d failures: %s\"\n                              (colorize [:bold :red] \"FAILURE\")\n                              (colorize [:bold :cyan] (str/join \" \" task))\n                              (colorize :cyan (count targets))\n                              (u/human-duration @elapsed)\n                              (count failures)\n                              (str/join \" \" failures)))\n          (lein/info (format \"\\n%s: Applied %s to %s projects in %s\"\n                             (colorize [:bold :green] \"SUCCESS\")\n                             (colorize [:bold :cyan] (str/join \" \" task))\n                             (colorize :cyan (count targets))\n                             (u/human-duration @elapsed))))))))\n"
  },
  {
    "path": "src/lein_monolith/task/fingerprint.clj",
    "content": "(ns lein-monolith.task.fingerprint\n  (:require\n    [clojure.edn :as edn]\n    [clojure.java.io :as io]\n    [clojure.string :as str]\n    [lein-monolith.color :refer [colorize]]\n    [lein-monolith.dependency :as dep]\n    [lein-monolith.plugin :as plugin]\n    [lein-monolith.target :as target]\n    [lein-monolith.task.util :as u]\n    [leiningen.core.main :as lein])\n  (:import\n    (java.io\n      File\n      InputStream)\n    java.security.MessageDigest\n    java.util.Base64))\n\n\n;; ## Options\n\n(def task-opts\n  (assoc target/selection-opts\n         :upstream 0\n         :downstream 0\n         :debug 0))\n\n\n;; ## Hashing projects' inputs\n\n(defn- base64\n  [^bytes content]\n  (-> (Base64/getUrlEncoder)\n      (.withoutPadding)\n      (.encode content)\n      (String.)))\n\n\n(defn- sha1\n  \"Takes a string or an InputStream, and returns a base64 string representing the\n  SHA-1 hash.\"\n  [content]\n  (let [hasher (MessageDigest/getInstance \"SHA-1\")]\n    (cond\n      (string? content)\n      (.update hasher (.getBytes ^String content))\n\n      (instance? InputStream content)\n      (let [buffer (byte-array 4096)\n            in ^InputStream content]\n        (loop []\n          (let [n (.read in buffer 0 (alength buffer))]\n            (when (pos? n)\n              (.update hasher buffer 0 n)\n              (recur)))))\n\n      :else\n      (throw (ex-info\n               (str \"Cannot compute digest from \" (type content))\n               {})))\n    (base64 (.digest hasher))))\n\n\n(defn- kv-hash\n  \"Takes a map from strings (ids of things we hashed) to strings (their hash\n  results); returns a new hash that identifies the aggregate\n  collection.\"\n  [kind m]\n  {:pre [(every? string? (vals m))]}\n  (->> (seq m)\n       (sort-by key)\n       (map #(str (key %) \\tab (val %)))\n       (str/join \"\\n\")\n       (str \"v2/\" (name kind) \"\\n\")\n       (sha1)))\n\n\n(defn- list-all-files\n  [^File file]\n  (if (.isFile file)\n    [file]\n    (mapcat list-all-files (.listFiles file))))\n\n\n(defn- local-path\n  [project ^File file]\n  (let [root (:root project)\n        path (.getAbsolutePath file)]\n    (when-not (str/starts-with? path root)\n      (throw (ex-info \"Cannot determine local path with different root\" {})))\n    (subs path (count root))))\n\n\n(defn- all-paths\n  \"Finds all source, test, and resource paths associated with a project, including\n  those set in profiles.\"\n  [project]\n  (->> (concat\n         [project]\n         (vals (:profiles project)))\n       (mapcat (juxt :source-paths :java-source-paths :test-paths :resource-paths))\n       (mapcat identity)\n       (map (fn absolute-file\n              [dir-str]\n              ;; Monolith subprojects and profiles don't use absolute paths\n              (if (str/starts-with? dir-str (:root project))\n                (io/file dir-str)\n                (io/file (:root project) dir-str))))))\n\n\n(defn- hash-sources\n  [project]\n  (->> (all-paths project)\n       (mapcat list-all-files)\n       (map (fn hash-file\n              [^File file]\n              [(local-path project file)\n               (with-open [in (io/input-stream file)]\n                 (sha1 in))]))\n       (into {})\n       (kv-hash :files)))\n\n\n(defn- dependency-coordinate-map\n  \"Turn a sequence of dependency vectors into a map from dependency symbols to\n  coordinate maps, with the version in `:version` and other qualifiers added as\n  entries.\"\n  [dependencies]\n  (into (sorted-map)\n        (map\n          (fn dep-entry\n            [[dep-sym version & {:as extra}]]\n            [(if (namespace dep-sym)\n               dep-sym\n               (symbol (name dep-sym) (name dep-sym)))\n             (assoc extra :version version)]))\n        dependencies))\n\n\n(defn- hash-dependency-coordinate\n  \"Hash a single dependency coordinate.\"\n  [coordinate]\n  (kv-hash\n    :dep-coordinate\n    (into {}\n          (map (juxt key (comp pr-str val)))\n          coordinate)))\n\n\n(defn- hash-profile-dependencies\n  \"Given a map of profiles, construct a hash of profile name/dependency type\n  keys to hashes.\"\n  [profiles]\n  (into {}\n        (comp\n          (mapcat\n            (fn lookup-deps\n              [[prof-key profile]]\n              [(when-let [deps (seq (:dependencies profile))]\n                 [prof-key :dependencies (dependency-coordinate-map deps)])\n               (when-let [deps (seq (:managed-dependencies profile))]\n                 [prof-key :managed-dependencies (dependency-coordinate-map deps)])]))\n          (remove nil?)\n          (map\n            (fn hash-deps\n              [[prof-key dep-key deps]]\n              [(str prof-key \\tab dep-key)\n               (->> deps\n                    (into {} (map (juxt key (comp hash-dependency-coordinate val))))\n                    (kv-hash :profile-dependencies))])))\n        profiles))\n\n\n(defn- hash-dependencies\n  \"Hashes a project's dependencies and managed dependencies, as well as that of\n  its profiles and project root.\"\n  [project]\n  (->> (assoc (:profiles project) ::default project)\n       (hash-profile-dependencies)\n       (map #(str (key %) \\tab (val %)))\n       (sort)\n       (str/join \"\\n\")\n       (str \"v3/dependencies\\n\")\n       (sha1)))\n\n\n(declare hash-inputs)\n\n\n(defn- hash-upstream-projects\n  [project dep-map subprojects cache]\n  (into (sorted-map)\n        (keep\n          (fn hash-upstream\n            [subproject-name]\n            (when-let [subproject (subprojects subproject-name)]\n              [subproject-name\n               (::final (hash-inputs subproject dep-map subprojects cache))])))\n        (dep-map (dep/project-name project))))\n\n\n(defn- hash-jar-exclusions\n  [project]\n  (kv-hash\n    :jar-exclusions\n    (into\n      {}\n      (map (fn [[profile-key profile]]\n             [profile-key (pr-str (:jar-exclusions profile))]))\n      (assoc (:profiles project) ::default project))))\n\n\n(defn- hash-inputs\n  \"Hashes each of a project's inputs, and returns a map containing each individual\n  result, so it's easier to explain what aspect of a project caused its overall\n  fingerprint to change.\n\n  Returns a map of `{::xyz \\\"hash\\\"}`\n\n  Keeps a cache of hashes computed so far, for efficiency.\"\n  [project dep-map subprojects cache]\n  (let [project-name (dep/project-name project)]\n    (or (@cache project-name)\n        (let [upstream-hashes\n              (hash-upstream-projects project dep-map subprojects cache)\n\n              prints\n              {::version (str (:version project))\n               ::java-version (System/getProperty \"java.version\")\n               ::jar-exclusions (hash-jar-exclusions project)\n               ::seed (str (:monolith/fingerprint-seed project 0))\n               ::sources (hash-sources project)\n               ::deps (hash-dependencies project)\n               ::upstream (kv-hash :projects upstream-hashes)}\n\n              prints\n              (assoc prints\n                     ::upstream-hashes upstream-hashes\n                     ::final (kv-hash :inputs prints)\n                     ::time (System/currentTimeMillis))]\n          (swap! cache assoc project-name prints)\n          prints))))\n\n\n;; ## Storing fingerprints\n\n;; The .lein-monolith-fingerprints file at the metaproject root stores the\n;; detailed fingerprint map for each project and marker type.\n\n(comment\n  ;; Example .lein-monolith-fingerprints\n  {\"build\" {'foo/bar {::sources \"abcde\"\n                      ,,,\n                      ::final \"vwxyz\"}\n            ,,,}\n   ,,,})\n\n\n(defn- fingerprints-file\n  ^File\n  [root]\n  (io/file root \".lein-monolith-fingerprints\"))\n\n\n(defn- read-fingerprints-file\n  [root]\n  (let [f (fingerprints-file root)]\n    (when (.exists f)\n      (edn/read-string (slurp f)))))\n\n\n(defn- write-fingerprints-file!\n  [root fingerprints]\n  (let [file (fingerprints-file root)]\n    (spit file (pr-str fingerprints))))\n\n\n(let [lock (Object.)]\n  (defn update-fingerprints-file!\n    [root f & args]\n    (locking lock\n      (write-fingerprints-file!\n        root\n        (apply f (read-fingerprints-file root) args)))))\n\n\n;; ## Generating and comparing fingerprints\n\n(defn context\n  \"Create a stateful context to use for fingerprinting operations.\"\n  [monolith subprojects]\n  (let [dep-map (dep/dependency-map subprojects)\n        root (:root monolith)\n        initial (read-fingerprints-file root)\n        cache (atom {})\n        subprojects (into {}\n                          (map\n                            (fn inherit-profiles\n                              [[k subproject]]\n                              [k\n                               (update subproject :profiles merge\n                                       (into {}\n                                             (plugin/build-profiles\n                                               monolith subproject)))]))\n                          subprojects)]\n    {:root root\n     :subprojects subprojects\n     :dependencies dep-map\n     :initial initial\n     :cache cache}))\n\n\n(defn- fingerprints\n  \"Returns a map of fingerpints associated with a project, including the ::final\n  one. Can be compared with a previous fingerprint file.\"\n  [ctx project-name]\n  (let [{:keys [subprojects dependencies cache]} ctx]\n    (hash-inputs (subprojects project-name) dependencies subprojects cache)))\n\n\n(defn changed?\n  \"Determines if a project has changed since the last fingerprint saved under the\n  given marker.\"\n  [ctx marker project-name]\n  (let [current (fingerprints ctx project-name)\n        past (get-in ctx [:initial marker project-name])]\n    (not= (::final past) (::final current))))\n\n\n(def ^:private fingerprint-priority\n  \"Priority ordered list of fingerprints to check; keys appearing earlier in\n  the list will take precedence when explaining why a project is considered\n  changed.\"\n  [::version\n   ::seed\n   ::sources\n   ::deps\n   ::java-version\n   ::jar-exclusions\n   ::upstream])\n\n\n(def ^:private reason-details\n  {::up-to-date [\"is up-to-date\" \"are up-to-date\" :green]\n   ::new-project [\"is a new project\" \"are new projects\" :red]\n   ::version [\"has a different version\" \"have different versions\" :red]\n   ::java-version [\"has a different java version\" \"have different java versions\" :red]\n   ::jar-exclusions [\"has different JAR exclusions\" \"have different JAR exclusions\" :yellow]\n   ::seed [\"has a different seed\" \"have different seeds\" :yellow]\n   ::sources [\"has updated sources\" \"have updated sources\" :red]\n   ::deps [\"has updated external dependencies\" \"have updated external dependencies\" :yellow]\n   ::upstream [\"is downstream of an affected project\" \"are downstream of affected projects\" :yellow]\n   ::unknown [\"has a different fingerprint\" \"have different fingerprints\" :red]})\n\n\n(defn- explain-kw\n  [ctx marker project-name]\n  (let [current (fingerprints ctx project-name)\n        past (get-in ctx [:initial marker project-name])]\n    (cond\n      (nil? past)\n      ::new-project\n\n      (= (::final past) (::final current))\n      ::up-to-date\n\n      :else\n      (or (some\n            (fn [ftype]\n              (when (not= (ftype past) (ftype current))\n                ftype))\n            fingerprint-priority)\n          ::unknown))))\n\n\n(defn explain-str\n  [ctx marker project-name]\n  (let [[singular _ color] (reason-details (explain-kw ctx marker project-name))]\n    (colorize color singular)))\n\n\n(defn save!\n  \"Save the fingerprints for a project with the specified marker.\"\n  [ctx marker project-name]\n  (let [current (fingerprints ctx project-name)]\n    (update-fingerprints-file!\n      (:root ctx) assoc-in [marker project-name] current)))\n\n\n(defn- list-projects\n  [project-names color]\n  (->> project-names\n       (map (partial colorize color))\n       (str/join \", \")))\n\n\n(defn- debug-project-fingerprints\n  \"Print a detailed representation of a project's fingerprints for debugging.\"\n  [project-name past current]\n  (let [all-attrs (disj (into (sorted-set)\n                              (concat (keys past)\n                                      (keys current)))\n                        ::upstream-hashes\n                        ::final\n                        ::time)\n        ordered-attrs (into []\n                            (filter all-attrs)\n                            fingerprint-priority)\n        compare-attrs (concat\n                        ordered-attrs\n                        (sort (remove (set ordered-attrs) all-attrs))\n                        [::final])\n        render-attr (fn render-attr\n                      [old-val new-val same-color]\n                      (cond\n                        (and (nil? old-val) new-val)\n                        (colorize :green new-val)\n\n                        (and old-val (nil? new-val))\n                        (colorize :red old-val)\n\n                        (= old-val new-val)\n                        (if same-color\n                          (colorize same-color new-val)\n                          new-val)\n\n                        :else\n                        (str (colorize :red old-val)\n                             \" => \"\n                             (colorize :green new-val))))]\n    (println project-name)\n    (doseq [attr compare-attrs]\n      (printf \"%24s: %s\\n\"\n              (colorize :cyan (name attr))\n              (render-attr (get past attr)\n                           (get current attr)\n                           nil))\n      (when (= ::upstream attr)\n        (let [past-map (::upstream-hashes past)\n              curr-map (::upstream-hashes current)]\n          (doseq [upstream (into (sorted-set)\n                                 (concat (keys past-map)\n                                         (keys curr-map)))]\n            (printf \"%16s * %s: %s\\n\"\n                    \" \" upstream\n                    (render-attr (get past-map upstream)\n                                 (get curr-map upstream)\n                                 :yellow)))))))\n  (newline)\n  (flush))\n\n\n(defn changed\n  [project opts markers]\n  (let [[monolith subprojects] (u/load-monolith! project)\n        ctx (context monolith subprojects)\n        targets (filter subprojects (target/select monolith subprojects opts))\n        markers (if (seq markers)\n                  markers\n                  (keys (:initial ctx)))]\n    (cond\n      (empty? markers) (lein/info \"No saved fingerprint markers\")\n      (empty? targets) (lein/info \"No projects selected\")\n\n      :else\n      (doseq [marker markers\n              :let [changed (->> targets\n                                 (filter (partial changed? ctx marker))\n                                 (set))\n                    pct-changed (if (seq targets)\n                                  (* 100.0 (/ (count changed) (count targets)))\n                                  0.0)]]\n        (lein/info (colorize\n                     (cond\n                       (== 0.0 pct-changed) :green\n                       (< pct-changed 50) :yellow\n                       :else :red)\n                     (format \"%.2f%%\" pct-changed))\n                   \"out of\"\n                   (count targets)\n                   \"projects have out-of-date\"\n                   (colorize :bold marker)\n                   \"fingerprints:\\n\")\n        (let [reasons (group-by (partial explain-kw ctx marker) targets)]\n          (doseq [k (concat [::unknown\n                             ::new-project]\n                            fingerprint-priority\n                            [::up-to-date])]\n            (when-let [projs (seq (k reasons))]\n              (let [[singular plural color] (reason-details k)\n                    c (count projs)]\n                (lein/info \"*\" (colorize color (count projs))\n                           (str (if (= 1 c) singular plural)\n                                (when-not (#{::up-to-date ::upstream} k)\n                                  (str \": \" (list-projects projs color)))))\n                (when (and (:debug opts)\n                           (not= k ::new-project)\n                           (not= k ::up-to-date))\n                  (doseq [project-name projs]\n                    (debug-project-fingerprints\n                      project-name\n                      (get-in ctx [:initial marker project-name])\n                      (fingerprints ctx project-name))))))))\n        (lein/info)))))\n\n\n(defn mark-fresh\n  [project opts markers]\n  (when-not (seq markers)\n    (lein/abort \"Please specify one or more markers!\"))\n  (let [[monolith subprojects] (u/load-monolith! project)\n        ctx (context monolith subprojects)\n        targets (filter subprojects (target/select monolith subprojects opts))\n        fprints (->> targets\n                     (map\n                       (fn [project-name]\n                         [project-name (fingerprints ctx project-name)]))\n                     (into {}))]\n    (update-fingerprints-file!\n      (:root monolith)\n      (fn add-new-fingerprints\n        [all-fprints]\n        (reduce\n          #(update %1 %2 merge fprints)\n          all-fprints\n          markers)))\n    (lein/info (format \"Set %s markers for %s projects\"\n                       (colorize :bold (count markers))\n                       (colorize :bold (count targets))))))\n\n\n(defn show\n  [project marker targets]\n  (when-not (seq targets)\n    (lein/abort \"Please specify at least one project to show\"))\n  (let [[monolith subprojects] (u/load-monolith! project)\n        ctx (context monolith subprojects)\n        projects (->> {:in (set targets)}\n                      (target/select monolith subprojects)\n                      (dep/topological-sort (dep/dependency-map subprojects)))]\n    (doseq [project-name projects]\n      (debug-project-fingerprints\n        project-name\n        (get-in ctx [:initial marker project-name])\n        (fingerprints ctx project-name)))))\n\n\n(defn clear\n  [project opts markers]\n  (let [[monolith subprojects] (u/load-monolith! project)\n        ctx (context monolith subprojects)\n        markers (if (seq markers)\n                  (set markers)\n                  (set (keys (:initial ctx))))\n        targets (set (filter subprojects (target/select monolith subprojects opts)))]\n    (update-fingerprints-file!\n      (:root monolith)\n      (partial into {}\n               (keep\n                 (fn [[marker fprints]]\n                   (if (markers marker)\n                     (when-let [fprints' (seq (filter (comp targets val) fprints))]\n                       [marker fprints'])\n                     [marker fprints])))))\n    (lein/info (format \"Cleared %s markers for %s projects\"\n                       (colorize :bold (count markers))\n                       (colorize :bold (count targets))))))\n"
  },
  {
    "path": "src/lein_monolith/task/graph.clj",
    "content": "(ns lein-monolith.task.graph\n  (:require\n    [clojure.java.io :as io]\n    [clojure.string :as str]\n    [lein-monolith.dependency :as dep]\n    [lein-monolith.target :as target]\n    [lein-monolith.task.util :as u]\n    [leiningen.core.main :as lein])\n  (:import\n    java.io.File))\n\n\n(def default-image-name\n  \"project-hierarchy.png\")\n\n\n(defn- mkparent!\n  \"Ensure the parent directory exists for the given file.\"\n  [^File file]\n  (.. file getCanonicalFile getParentFile mkdir))\n\n\n(defn cluster->descriptor\n  [monolith-root subdirectory]\n  (when (not= monolith-root subdirectory)\n    {:label (subs (str subdirectory)\n                  (inc (count monolith-root)))}))\n\n\n(defn graph\n  \"Generate a graph of subprojects and their interdependencies.\"\n  [project opts]\n  ;; NOTE: This is pulled in on-demand here because Rhizome needs to load the\n  ;; JVM's graphical context in order to render the hierarchy images. This has\n  ;; the unfortunate side-effect of popping up a Java applet in most OS's task\n  ;; bars, which can then steal focus away from the terminal. To keep this from\n  ;; happening on _every_ invocation of lein-monolith, only load it when then\n  ;; user actually wants to make graphs.\n  (require 'rhizome.viz)\n  (let [graph->dot (ns-resolve 'rhizome.dot 'graph->dot)\n        dot->image (ns-resolve 'rhizome.viz 'dot->image)\n        save-image (ns-resolve 'rhizome.viz 'save-image)\n        [monolith subprojects] (u/load-monolith! project)\n        targets (target/select monolith subprojects opts)\n        dependencies (dep/dependency-map subprojects)\n        dot-str (graph->dot\n                  targets\n                  dependencies\n                  :vertical? false\n                  :node->descriptor #(array-map :label (name %))\n                  :node->cluster (fn [id]\n                                   (when-let [root (get-in subprojects [id :root])]\n                                     (str/join \"/\" (butlast (str/split root #\"/\")))))\n                  :cluster->descriptor (partial cluster->descriptor (:root monolith)))]\n    (when (empty? targets)\n      (lein/abort \"No targets selected to graph!\"))\n    (when-let [dot-file (some-> (:dot-path opts) io/file)]\n      (mkparent! dot-file)\n      (spit dot-file dot-str)\n      (lein/info \"Wrote dependency graph data to\" (str dot-file)))\n    (when-let [graph-file (or (some-> (:image-path opts) io/file)\n                              (io/file (:target-path monolith) default-image-name))]\n      (mkparent! graph-file)\n      (save-image (dot->image dot-str) (str graph-file))\n      (lein/info \"Generated dependency graph image at\" (str graph-file)))))\n"
  },
  {
    "path": "src/lein_monolith/task/info.clj",
    "content": "(ns lein-monolith.task.info\n  (:require\n    [clojure.string :as str]\n    [lein-monolith.color :refer [colorize]]\n    [lein-monolith.config :as config]\n    [lein-monolith.dependency :as dep]\n    [lein-monolith.plugin :as plugin]\n    [lein-monolith.target :as target]\n    [lein-monolith.task.util :as u]\n    [leiningen.core.main :as lein]))\n\n\n(defn- inherited-tags\n  \"Builds tags for printing with the inherited properties, e.g. `(leaky, raw)`.\"\n  [{:keys [leaky? raw?]}]\n  (some->> (cond-> []\n             leaky? (conj \"leaky\")\n             raw? (conj \"raw\"))\n           seq\n           (str/join \", \")\n           (format \" (%s)\")))\n\n\n(defn- print-inherited-info\n  \"Show information about the inherited profiles present within the monorepo\n  configuration.\"\n  [monolith]\n  (doseq [[_profile-key {:keys [inherit-key] :as config}] plugin/profile-config]\n    (when-let [inherited (get-in monolith [:monolith inherit-key])]\n      (println (str \"Inherited properties\" (inherited-tags config) \":\"))\n      (doseq [kw inherited] (println (colorize [:bold :yellow] kw)))\n      (newline))))\n\n\n(defn info\n  \"Show information about the monorepo configuration.\"\n  [project opts]\n  (let [monolith (config/find-monolith! project)]\n    (when-not (:bare opts)\n      (println \"Monolith root:\" (:root monolith))\n      (newline)\n      (print-inherited-info monolith)\n      (when-let [dirs (get-in monolith [:monolith :project-dirs])]\n        (println \"Subproject directories:\")\n        (doseq [dir dirs]\n          (println (colorize :magenta dir)))\n        (newline)))\n    (let [subprojects (config/read-subprojects! monolith)\n          dependencies (dep/dependency-map subprojects)\n          targets (target/select monolith subprojects opts)\n          prefix-len (inc (count (:root monolith)))]\n      ;; IDEA: some kind of stats about dependency graph shape\n      (when-not (:bare opts)\n        (printf \"Internal projects (%d):\\n\" (count targets)))\n      (doseq [subproject-name (dep/topological-sort dependencies targets)\n              :let [{:keys [version root]} (get subprojects subproject-name)\n                    relative-path (subs (str root) prefix-len)]]\n        (if (:bare opts)\n          (println subproject-name relative-path)\n          (printf \"  %-90s   %s\\n\"\n                  (str (colorize :red \\[)\n                       subproject-name\n                       \\space\n                       (colorize :magenta (pr-str version))\n                       (colorize :red \\]))\n                  (colorize :cyan relative-path)))))))\n\n\n(defn lint\n  \"Check various aspects of the monolith and warn about possible problems.\"\n  [project opts]\n  (let [[_ subprojects] (u/load-monolith! project)]\n    (when (:deps opts)\n      (doseq [[dep-name coords] (->> (vals subprojects)\n                                     (mapcat dep/sourced-dependencies)\n                                     (group-by first))]\n        (dep/lint-dependency dep-name coords)))))\n\n\n(defn deps\n  \"Print a list of subprojects and the (internal) projects they depend on.\n  Targeting options may be used to scope down the projects listed.\"\n  [project opts]\n  (let [[monolith subprojects] (u/load-monolith! project)\n        targets (target/select monolith subprojects opts)\n        dependencies (dep/dependency-map subprojects)]\n    (doseq [project-name targets]\n      (doseq [dependency (get dependencies project-name)]\n        (when (if (contains? subprojects dependency)\n                (u/parse-bool (:internal opts \"true\"))\n                (u/parse-bool (:external opts \"false\")))\n          (if (:bare opts)\n            (printf \"%s\\t%s\\n\" project-name dependency)\n            (println (colorize :bold project-name) \"->\" dependency)))))))\n\n\n(defn deps-on\n  \"Print a list of subprojects which depend on the given package(s). Defaults\n  to the current project if none are provided.\"\n  [project opts project-names]\n  (let [[_ subprojects] (u/load-monolith! project)\n        dep-map (dep/dependency-map subprojects)\n        resolved-names (map (partial dep/resolve-name! (keys dep-map))\n                            project-names)]\n    (doseq [dep-name resolved-names]\n      (when-not (get dep-map dep-name)\n        (lein/abort dep-name \"is not a valid subproject!\"))\n      (when-not (:bare opts)\n        (lein/info (str \"\\nSubprojects which \"\n                        (when (:transitive opts) \"transitively \")\n                        \"depend on \"\n                        (colorize [:bold :yellow] dep-name))))\n      (let [match-names (if (:transitive opts)\n                          (dep/downstream-keys dep-map dep-name)\n                          #{dep-name})]\n        (doseq [subproject-name (dep/topological-sort dep-map)]\n          (when-let [spec (->> (get-in subprojects [subproject-name :dependencies])\n                               (filter (comp match-names dep/condense-name first))\n                               (first))]\n            (if (:bare opts)\n              (println subproject-name (first spec) (second spec))\n              (println \"  \" (colorize :bold subproject-name)\n                       \"->\" (colorize :bold spec)))))))))\n\n\n(defn deps-of\n  \"Print a list of subprojects which given package(s) depend on. Defaults to\n  the current project if none are provided.\"\n  [project opts project-names]\n  (let [[_ subprojects] (u/load-monolith! project)\n        dep-map (dep/dependency-map subprojects)\n        resolved-names (map (partial dep/resolve-name! (keys dep-map))\n                            project-names)]\n    (doseq [project-name resolved-names]\n      (when-not (get dep-map project-name)\n        (lein/abort project-name \"is not a valid subproject!\"))\n      (when-not (:bare opts)\n        (lein/info \"\\nSubprojects which\" (colorize [:bold :yellow] project-name)\n                   (if (:transitive opts)\n                     \"transitively depends on\"\n                     \"depends on\")))\n      (doseq [dep (if (:transitive opts)\n                    (-> (dep/upstream-keys dep-map project-name)\n                        (disj project-name)\n                        (->> (dep/topological-sort dep-map)))\n                    (filter #(contains? dep-map %) (dep-map project-name)))]\n        (if (:bare opts)\n          (println project-name dep)\n          (println \"  \" (colorize :bold project-name)\n                   \"->\" dep))))))\n"
  },
  {
    "path": "src/lein_monolith/task/util.clj",
    "content": "(ns lein-monolith.task.util\n  \"Utility functions for task code.\"\n  (:require\n    [clojure.string :as str]\n    [lein-monolith.config :as config]\n    [leiningen.core.main :as lein]))\n\n\n(defn shell-escape\n  \"Escape the provided argument for use in a shell.\"\n  [arg]\n  (let [s (if (string? arg)\n            arg\n            (pr-str arg))]\n    (if (or (str/includes? s \" \")\n            (str/includes? s \"'\")\n            (str/includes? s \"\\\"\"))\n      (str \\' (str/escape s {\\' \"\\\\'\"}) \\')\n      s)))\n\n\n(defn parse-kw-args\n  \"Given a sequence of string arguments, parse out expected keywords. Returns\n  a vector with a map of keywords to values (or `true` for flags) followed by\n  a sequence the remaining unparsed arguments.\"\n  [expected args]\n  (loop [opts {}\n         args args]\n    (if-not (and (first args) (.startsWith (str (first args)) \":\"))\n      ;; No more arguments to process, or not a keyword arg.\n      [opts args]\n      ;; Parse keyword option arg.\n      (let [kw (keyword (subs (first args) 1))\n            multi-arg-count (get expected (keyword (str (name kw) \\*)))\n            arg-count (get expected kw multi-arg-count)]\n        (cond\n          ;; Unexpected option, halt parsing.\n          (nil? arg-count)\n          [opts args]\n\n          ;; Flag option.\n          (zero? arg-count)\n          (recur (assoc opts kw true) (rest args))\n\n          ;; Single-valued option.\n          (and (= 1 arg-count) (nil? multi-arg-count))\n          (recur (assoc opts kw (first (rest args))) (drop 2 args))\n\n          ;; Multi-spec option, join value list.\n          multi-arg-count\n          (recur (update opts kw (fnil into []) (take arg-count (rest args)))\n                 (drop (inc arg-count) args))\n\n          ;; Multi-arg (but not spec) option.\n          :else\n          (recur (assoc opts kw (vec (take arg-count (rest args))))\n                 (drop (inc arg-count) args)))))))\n\n\n(defn parse-bool\n  \"Parse a boolean option with some slack for human-friendliness.\"\n  [x]\n  (case (str/lower-case (str x))\n    (\"true\" \"t\" \"yes\" \"y\") true\n    (\"false\" \"f\" \"no\" \"n\") false\n    (throw (IllegalArgumentException.\n             (format \"%s is not a valid boolean value\" (pr-str x))))))\n\n\n(defn globalize-opts\n  \"Takes a map of parsed options, and converts the `:upstream` and `:downstream`\n  options into `:upstream-of <project>` and `:downstream-of <project>`.\"\n  [project opts]\n  (if (and (:monolith project) (or (:upstream opts) (:downstream opts)))\n    (do (lein/warn \"The :upstream and :downstream options have no meaning in the monolith project.\")\n        opts)\n    (cond-> opts\n      (:upstream opts) (update :upstream-of conj (:name project))\n      (:downstream opts) (update :downstream-of conj (:name project)))))\n\n\n(defn stopwatch\n  \"Construct a timer which will contain the number of milliseconds elapsed\n  between its creation and when it is dereferenced.\"\n  []\n  (let [start (System/nanoTime)]\n    (delay (/ (- (System/nanoTime) start) 1e6))))\n\n\n(defn human-duration\n  \"Renders a duration in milliseconds in hour:minute:second.ms format.\"\n  [duration]\n  (if duration\n    (let [hours (int (/ duration 1000.0 60 60))\n          minutes (- (int (/ duration 1000.0 60))\n                     (* hours 60))\n          seconds (- (int (/ duration 1000.0))\n                     (* minutes 60)\n                     (* hours 60 60))\n          milliseconds (int (rem duration 1000))]\n      (if (pos? hours)\n        (format \"%d:%02d:%02d.%03d\" hours minutes seconds milliseconds)\n        (format \"%d:%02d.%03d\" minutes seconds milliseconds)))\n    \"--:--\"))\n\n\n(defn load-monolith!\n  \"Helper function to make a common pattern more succinct.\"\n  [project]\n  (let [monolith (config/find-monolith! project)\n        subprojects (config/read-subprojects! monolith)]\n    [monolith subprojects]))\n"
  },
  {
    "path": "src/lein_monolith/task/with_dependency_set.clj",
    "content": "(ns lein-monolith.task.with-dependency-set\n  (:require\n    [lein-monolith.plugin :as plugin]\n    [lein-monolith.task.util :as u]\n    [leiningen.core.main :as lein]))\n\n\n(defn run-task\n  \"Runs the given task on the project with the given dependency set by reloading\n   the project, changing the dependencies, re-initializing the project, and then\n   running the task.\"\n  [project dependency-set task]\n  (let [[monolith _] (u/load-monolith! project)\n        dependencies (or (get-in monolith [:monolith :dependency-sets dependency-set])\n                         (lein/abort (format \"Unknown dependency set %s\" dependency-set)))\n        managed-deps {:managed-dependencies (vary-meta dependencies assoc :replace true)}\n        profile (merge managed-deps\n                       (when-not (:monolith project)\n                         {:monolith/dependency-set dependency-set}))\n        project (plugin/add-active-profile project :monolith/dependency-override profile)]\n    (lein/resolve-and-apply project task)))\n"
  },
  {
    "path": "src/leiningen/monolith.clj",
    "content": "(ns leiningen.monolith\n  \"Leiningen task implementations for working with monorepos.\"\n  (:require\n    [clojure.string :as str]\n    [lein-monolith.dependency :as dep]\n    [lein-monolith.plugin :as plugin]\n    [lein-monolith.target :as target]\n    [lein-monolith.task.checkouts :as checkouts]\n    [lein-monolith.task.each :as each]\n    [lein-monolith.task.fingerprint :as fingerprint]\n    [lein-monolith.task.graph :as graph]\n    [lein-monolith.task.info :as info]\n    [lein-monolith.task.util :as u]\n    [lein-monolith.task.with-dependency-set :as wds]\n    [leiningen.core.main :as lein]))\n\n\n(defn- opts-only\n  [expected args]\n  (let [[opts more] (u/parse-kw-args expected args)]\n    (when (seq more)\n      (lein/abort \"Unknown args:\" (str/join \" \" more)))\n    opts))\n\n\n(defn- opts+projects\n  [expected project args]\n  (let [[opts more] (u/parse-kw-args expected args)\n        project-names (or (seq (map read-string more))\n                          [(dep/project-name project)])]\n    [opts project-names]))\n\n\n;; ## Subtask Vars\n\n(defn info\n  \"Show information about the monorepo configuration.\n\n  Options:\n    :bare        Only print the project names and directories, one per line\n    (targets)    Standard target selection options are supported\"\n  [project args]\n  (info/info project (opts-only (merge target/selection-opts {:bare 0}) args)))\n\n\n(defn lint\n  \"Check various aspects of the monolith and warn about possible problems.\n\n  Options:\n    :deps        Check for conflicting dependency versions\"\n  [project args]\n  (info/lint project (if (seq args) (opts-only {:deps 0} args) {:deps true})))\n\n\n(defn deps\n  \"Print a list of subprojects and the (internal) projects they depend on.\n  Targeting options may be used to scope down the projects listed.\n\n  Options:\n    :internal <bool>   Whether to show dependencies on internal projects (default: true)\n    :external <bool>   Whether to show dependencies on external projects (default: false)\n    :bare              Only print the project names and dependencies, one per line\n    (targets)          Standard target selection options are supported\"\n  [project args]\n  (let [opts (opts-only (assoc target/selection-opts\n                               :internal 1\n                               :external 1\n                               :bare 0)\n                        args)]\n    (info/deps project opts)))\n\n\n(defn deps-on\n  \"Print a list of subprojects which depend on the given package(s). Defaults\n  to the current project if none are provided.\n\n  Options:\n    :bare          Only print the project names and dependent versions, one per line\n    :transitive    Include transitive dependents in addition to direct ones\"\n  [project args]\n  (let [[opts project-names] (opts+projects {:bare 0, :transitive 0} project args)]\n    (info/deps-on project opts project-names)))\n\n\n(defn deps-of\n  \"Print a list of subprojects which given package(s) depend on. Defaults to\n  the current project if none are provided.\n\n  Options:\n    :bare          Only print the project names and dependent versions, one per line\n    :transitive    Include transitive dependencies in addition to direct ones\"\n  [project args]\n  (let [[opts project-names] (opts+projects {:bare 0, :transitive 0} project args)]\n    (info/deps-of project opts project-names)))\n\n\n(defn graph\n  \"Generate a graph of subprojects and their interdependencies.\n\n  Options:\n    :image-path <path>   Path to save the graph image to (default: `project-hierarchy.png`)\n    :dot-path <path>     Path to save the raw dot file to (default: not output)\n    (targets)            Standard target selection options are supported\"\n  [project args]\n  (graph/graph\n    project\n    (opts-only (assoc target/selection-opts\n                      :image-path 1\n                      :dot-path 1)\n               args)))\n\n\n(defn ^:higher-order with-all\n  \"Apply the given task with a merged set of dependencies, sources, and tests\n  from all the internal projects.\n\n  For example:\n\n      lein monolith with-all test\"\n  [project args]\n  (when-not (:monolith project)\n    (lein/warn \"WARN: Running with-all in a subproject is not recommended! Beware of dependency ordering differences.\"))\n  (let [[opts [task & args]] (u/parse-kw-args target/selection-opts args)\n        [monolith subprojects] (u/load-monolith! project)\n        targets (target/select monolith subprojects opts)\n        profile (plugin/merged-profile monolith (select-keys subprojects targets))\n        project (reduce-kv\n                  (fn remove-replace-meta\n                    [proj k _v]\n                    (update proj k vary-meta dissoc :replace))\n                  project profile)]\n    (lein/apply-task\n      task\n      (plugin/add-active-profile project :monolith/all profile)\n      args)))\n\n\n(defn ^:higher-order each\n  \"Iterate over a target set of subprojects in the monolith and apply the given\n  task. Projects are iterated in dependency order; that is, later projects may\n  depend on earlier ones.\n\n  By default, all projects are included in the set of iteration targets. If you\n  provide the `:in`, `:upstream[-of]`, or `:downstream[-of]` options then the\n  resulting set of projects will be composed only of the additive targets of\n  each of the options specified. The `:skip` option can be used to exclude\n  specific projects from the set. Specifying `:select` will use a configured\n  `:project-selector` to filter the final set.\n\n  If the iteration fails on a subproject, you can continue where you left off\n  by providing the `:start` option as the first argument, giving the name of the\n  project to resume from.\n\n  General Options:\n    :parallel <threads>     Run tasks in parallel across a fixed thread pool.\n    :endure                 Continue executing the task even if some subprojects fail.\n    :report                 Print a detailed timing report after running tasks.\n    :silent                 Don't print task output unless a subproject fails.\n    :output <path>          Save each project's individual output in the given directory.\n\n  Targeting Options:\n    :in <names>             Add the named projects directly to the targets.\n    :upstream               Add the transitive dependencies of the current project to the targets.\n    :upstream-of <names>    Add the transitive dependencies of the named projects to the targets.\n    :downstream             Add the transitive consumers of the current project to the targets.\n    :downstream-of <names>  Add the transitive consumers of the named projects to the targets.\n    :select <key>           Use a selector from the config to filter target projects.\n    :skip <names>           Exclude one or more projects from the target set.\n    :start <name>           Provide a starting point for the subproject iteration\n    :refresh <marker>       Only iterate over projects that have changed since the last `:refresh` of this marker\n    :changed <marker>       Like `:refresh` but does not reset the projects' state for the next run\n\n  Each <names> argument can contain multiple comma-separated project names, and\n  all the targeting options except `:start` may be provided multiple times.\n\n  Examples:\n\n      lein monolith each check\n      lein monolith each :upstream :parallel 4 install\n      lein monolith each :select :deployable uberjar\n      lein monolith each :report :start my/lib-a test\n      lein monolith each :refresh ci/build install\"\n  [project args]\n  (let [[opts task] (u/parse-kw-args each/task-opts args)]\n    (when (empty? task)\n      (lein/abort \"Cannot run each without a task argument!\"))\n    (when (and (:start opts) (:parallel opts))\n      (lein/abort \"The :parallel and :start options are not compatible!\"))\n    (each/run-tasks project opts task)))\n\n\n(defn link\n  \"Create symlinks in the checkouts directory pointing to all internal\n  dependencies in the current project. Optionally, a set of project names may\n  be specified to create links to only those projects (this implies `:deep`).\n\n  Options:\n    :force       Override any existing checkout links with conflicting names\n    :deep        Link all subprojects this project transitively depends on\"\n  [project args]\n  (when (:monolith project)\n    (lein/abort \"The 'link' task cannot be run for the monolith project!\"))\n  (let [[opts project-names] (opts+projects {:force 0, :deep 0} project args)\n        target-names (remove #(= (dep/project-name project) %)\n                             project-names)]\n    (checkouts/link project opts target-names)))\n\n\n(defn unlink\n  \"Remove internal checkout links from a project. Optionally, a set of project\n  names may be specified to remove links only for those projects.\n\n  Options:\n    :all         Remove all checkouts, not just internal ones.\"\n  [project args]\n  (when (:monolith project)\n    (lein/abort \"The 'unlink' task cannot be run for the monolith project!\"))\n  (let [[opts project-names] (opts+projects {:all 0} project args)\n        target-names (remove #(= (dep/project-name project) %)\n                             project-names)]\n    (checkouts/unlink project opts target-names)))\n\n\n;; ## Fingerprinting\n\n(defn changed\n  \"Show information about the projects that have changed since last :refresh.\n\n  Optionally takes one or more marker ids, or project selectors, to narrow the\n  information.\n\n  Usage:\n  lein monolith changed [:debug] [project-selectors] [marker1 marker2 ...]\"\n  [project args]\n  (let [[opts more] (u/parse-kw-args fingerprint/task-opts args)\n        opts (u/globalize-opts project opts)]\n    (fingerprint/changed project opts more)))\n\n\n(defn mark-fresh\n  \"Manually mark projects as refreshed.\n\n  Fingerprints all projects, or a selected set of projects, and saves the\n  results under the given marker id(s), for later use with the `:refresh`\n  selector.\n\n  Usage:\n  lein monolith mark [project-selectors] marker1 marker2 ...\"\n  [project args]\n  (let [[opts more] (u/parse-kw-args fingerprint/task-opts args)\n        opts (u/globalize-opts project opts)]\n    (fingerprint/mark-fresh project opts more)))\n\n\n(defn show-fingerprints\n  \"Show information about the calculation of one or more projects'\n  fingerprints, compared to a current marker.\n\n  Usage:\n  lein monolith show-fingerprints marker project [...]\"\n  [project [marker & args]]\n  (fingerprint/show project marker args))\n\n\n(defn clear-fingerprints\n  \"Clear projects' cached fingerprints so they will be re-built next :refresh.\n\n  Removes the fingerprints associated with one or more marker types on one or\n  more projects. By default, clears all projects for all marker types.\n\n  Usage:\n  lein monolith clear [project-selectors] [marker1 marker2 ...]\"\n  [project args]\n  (let [[opts more] (u/parse-kw-args fingerprint/task-opts args)\n        opts (u/globalize-opts project opts)]\n    (fingerprint/clear project opts more)))\n\n\n(defn ^:higher-order with-dependency-set\n  \"Run a task with a set of managed dependencies from a named dependency set.\n\n   Overrides the dependencies from the named dependency set into the project.\n   For the root project, this means the managed dependencies will be overwritten\n   with the dependencies from the named set. For subprojects, the\n   `:monolith/dependency-set` metadata key will be set to the named set.\n\n   Usage:\n   lein monolith with-dependency-set <dependency-set-name> <task> [...]\n   lein monolith each [opts] monolith with-dependency-set [...]\"\n  [project args]\n  (let [dependency-set (read-string (first args))\n        task (rest args)]\n    (when (some #{\"monolith\"} task)\n      (lein/abort (str \"Running monolith with-dependency-set as a top-level task\"\n                       \" produces undefined behavior. It should be used as a subtask.\")))\n    (wds/run-task project dependency-set task)))\n\n\n;; ## Plugin Entry\n\n(defn monolith\n  \"Tasks for working with Leiningen projects inside a monorepo.\"\n  {:subtasks [#'info #'lint #'deps #'deps-on #'deps-of #'graph\n              #'with-all #'each #'link #'unlink\n              #'changed #'mark-fresh #'show-fingerprints #'clear-fingerprints\n              #'with-dependency-set]}\n  [project command & args]\n  (case command\n    \"info\"                (info project args)\n    \"lint\"                (lint project args)\n    \"deps\"                (deps project args)\n    \"deps-on\"             (deps-on project args)\n    \"deps-of\"             (deps-of project args)\n    \"graph\"               (graph project args)\n    \"with-all\"            (with-all project args)\n    \"each\"                (each project args)\n    \"link\"                (link project args)\n    \"unlink\"              (unlink project args)\n    \"changed\"             (changed project args)\n    \"mark-fresh\"          (mark-fresh project args)\n    \"show-fingerprints\"   (show-fingerprints project args)\n    \"clear-fingerprints\"  (clear-fingerprints project args)\n    \"with-dependency-set\" (with-dependency-set project args)\n    (lein/abort (pr-str command) \"is not a valid monolith command! Try: lein help monolith\"))\n  (flush))\n"
  },
  {
    "path": "test/example-tests.sh",
    "content": "#!/bin/bash\n\n# Fail if any subcommand fails\nset -e\n\nREPO_ROOT=\"$(cd $(dirname \"${BASH_SOURCE[0]}\")/.. && pwd)\"\nPLUGIN_VERSION=\"$(head -1 project.clj | cut -d ' ' -f 3)\"\necho \"Installing lein-monolith $PLUGIN_VERSION from source...\"\ncd $REPO_ROOT\nlein install\n\nEXAMPLE_DIR=\"${REPO_ROOT}/example\"\ncd $EXAMPLE_DIR\necho\necho \"Updating example project to use lein-monolith version $PLUGIN_VERSION...\"\nsed -i'.bak' -e \"s/lein-monolith \\\"[^\\\"]*\\\"/lein-monolith $PLUGIN_VERSION/\" project.clj\n\necho\necho \"Running tests against example projects in $EXAMPLE_DIR\"\n\ntest_monolith() {\n    echo\n    echo -e \"\\033[36mlein monolith $@\\033[0m\"\n    lein monolith \"$@\"\n    echo\n}\n\ntest_monolith info\ntest_monolith lint\ntest_monolith deps\ntest_monolith deps-of app-a\ntest_monolith deps-on lib-a\ntest_monolith deps-of lib-c\ntest_monolith with-all pprint :dependencies :source-paths :test-paths\ntest_monolith each pprint :version\ntest_monolith each :in lib-a pprint :root :compile-path\ntest_monolith each :upstream-of lib-b pprint :version\ntest_monolith each :downstream-of lib-a pprint :name\ntest_monolith each :parallel 3 :report :endure pprint :group\ntest_monolith each :refresh foo install\ntest_monolith each :refresh foo install\ntest_monolith each :parallel 3 :refresh bar install\ntest_monolith changed\ntest_monolith clear-fingerprints :upstream-of lib-b\ntest_monolith mark-fresh :upstream-of lib-b foo bar\n"
  },
  {
    "path": "test/lein_monolith/config_test.clj",
    "content": "(ns lein-monolith.config-test\n  (:require\n    [clojure.java.io :as io]\n    [clojure.test :refer [deftest is testing]]\n    [lein-monolith.config :as config]\n    [lein-monolith.test-utils :refer [read-example-project]]))\n\n\n(deftest read-subprojects\n  (testing \"subproject clean targets use absolute paths\"\n    (let [monolith (read-example-project)\n          subprojects (config/read-subprojects! monolith)\n          test-project (get subprojects 'lein-monolith.example/lib-b)\n          clean-targets (:clean-targets test-project)]\n      (is (map? test-project)\n          \"lib-b subproject was loaded\")\n      (is (= {:protect false} (meta clean-targets))\n          \"metadata is preserved\")\n      (is (= (set clean-targets) (set (filter #(.isAbsolute (io/file %)) clean-targets)))\n          \"All clean target paths are absolute\"))))\n"
  },
  {
    "path": "test/lein_monolith/dependency_test.clj",
    "content": "(ns lein-monolith.dependency-test\n  (:require\n    [clojure.set :as set]\n    [clojure.string :as str]\n    [clojure.test :refer [deftest testing is are]]\n    [lein-monolith.dependency :as dep])\n  (:import\n    (clojure.lang\n      IExceptionInfo)))\n\n\n(deftest coordinate-utilities\n  (testing \"condense-name\"\n    (is (nil? (dep/condense-name nil)))\n    (is (= 'lein-monolith (dep/condense-name 'lein-monolith/lein-monolith)))\n    (is (= 'example/foo (dep/condense-name 'example/foo))))\n  (testing \"project-name\"\n    (is (nil? (dep/project-name nil)))\n    (is (= 'foo (dep/project-name {:group \"foo\", :name \"foo\"})))\n    (is (= 'example/bar (dep/project-name {:group \"example\", :name \"bar\"}))))\n  (testing \"resolve-name\"\n    (let [projects '[foo baz/foo example/bar example/baz]]\n      (is (nil? (dep/resolve-name projects 'qux)))\n      (is (nil? (dep/resolve-name projects 'example/qux)))\n      (is (= '[foo/baz bar/baz] (dep/resolve-name '[foo/baz bar/baz bar/qux] 'baz)))\n      (is (= 'foo (dep/resolve-name projects 'foo)))\n      (is (= 'foo (dep/resolve-name projects 'foo/foo)))\n      (is (= 'example/bar (dep/resolve-name projects 'bar)))\n      (is (= 'baz/foo (dep/resolve-name projects 'baz/foo)))\n      (is (= 'example/baz (dep/resolve-name projects 'baz)))))\n  (testing \"clean-coord\"\n    (is (= '[example/foo \"1.0\"] (dep/clean-coord '[example/foo \"1.0\"])))\n    (is (= '[example/bar \"0.5.0\"]\n           (dep/clean-coord '[example/bar \"0.5.0\" :exclusions [foo]])))\n    (is (= '[example/bar \"0.5.0\"]\n           (dep/clean-coord '[example/bar \"0.5.0\" :exclusions [foo] :scope :test])))\n    (is (= '[example/baz \"0.1.0-SNAPSHOT\"]\n           (dep/clean-coord '[example/baz \"0.1.0-SNAPSHOT\" :scope :test]))))\n  (testing \"source-metadata\"\n    (is (nil? (dep/dep-source [:foo \"123\"])))\n    (is (= [:foo \"123\"] (dep/with-source [:foo \"123\"] 'example/bar)))\n    (is (= 'example/bar (dep/dep-source (dep/with-source [:foo \"123\"] 'example/bar))))))\n\n\n(deftest dependency-mapping\n  (let [projects '{foo/a {:dependencies []}\n                   foo/b {:dependencies [[foo/a \"1.0.0\"]]}\n                   foo/c {:dependencies [[foo/a \"1.0.0\"]]}\n                   foo/d {:dependencies [[foo/b \"1.0.0\"]\n                                         [foo/c \"1.0.0\"]]}}]\n    (is (= '{foo/a #{}, foo/b #{foo/a}, foo/c #{foo/a}, foo/d #{foo/b foo/c}}\n           (dep/dependency-map projects))))\n  (let [projects '{foo/a {:dependencies []}\n                   foo/b {:dependencies [] :profiles {:test {:dependencies [[foo/a]]}}}}]\n    (is (= `{foo/a #{}, foo/b #{foo/a}}\n           (dep/dependency-map projects)))))\n\n\n(deftest upstream-dependency-closure\n  (let [deps {:a #{}, :b #{:a}, :c #{:a :b} :x #{:b} :y #{:c}}]\n    (is (= #{:a} (dep/upstream-keys deps :a)))\n    (is (= #{:a :b} (dep/upstream-keys deps :b)))\n    (is (= #{:a :b :c} (dep/upstream-keys deps :c)))\n    (is (= #{:a :b :x} (dep/upstream-keys deps :x)))\n    (is (= #{:a :b :c :y} (dep/upstream-keys deps :y)))))\n\n\n(deftest downstream-dependency-closure\n  (let [deps {:a #{}, :b #{:a}, :c #{:a :b} :x #{:b} :y #{:c}}]\n    (is (= #{:a :b :c :x :y} (dep/downstream-keys deps :a)))\n    (is (= #{:b :c :x :y} (dep/downstream-keys deps :b)))\n    (is (= #{:c :y} (dep/downstream-keys deps :c)))\n    (is (= #{:x} (dep/downstream-keys deps :x)))\n    (is (= #{:y} (dep/downstream-keys deps :y))))\n  (let [deps {:a #{}, :b #{:a}, :c #{:a}, :d #{:b :c}}]\n    (is (= #{:a :b :c :d} (dep/downstream-keys deps :a)))))\n\n\n(defn maps-like\n  [n m]\n  (map #(into (array-map) (shuffle (seq %))) (repeat n m)))\n\n\n;; Int -> [Deps SmallestCycles]\n(defn gen-dep-cycle\n  \"Create a dependency cycle of the specified size\n  that also includes a cycle of length 3 (ie. between two deps).\n  Returns a vector of the dependencies and a set of its\n  smallest dependency cycles.\"\n  [size]\n  {:pre [(<= 5 size)]}\n  ;; comments assume size == 50\n  (let [[cstart cend] ((juxt identity inc) (quot size 2))]\n    [(into {}\n           (map (fn [a]\n                  [a\n                   (condp = a\n                     (dec size) #{0} ; 0->1-*>49->0\n                     cend #{cstart} ; 24->25->24\n                     (into #{} (range (inc a) size)))]))\n           (range size))\n     #{[cstart cend cstart]\n       [cend cstart cend]}]))\n\n\n(defn cycle-actually-occurs\n  [deps c]\n  {:pre [(vector? c)\n         (seq c)\n         (map? deps)\n         (= (first c) (peek c))]}\n  (boolean\n    (reduce (fn [downstream el]\n              (or (some-> el downstream deps)\n                  (reduced nil)))\n            (deps (first c))\n            (next c))))\n\n\n(deftest cycle-actually-occurs-test\n  (is (cycle-actually-occurs {1 #{2} 2 #{1}} [1 2 1]))\n  (is (not (cycle-actually-occurs {1 #{2} 2 #{3} 3 #{}} [1 2 1]))))\n\n\n(deftest unique-cycles-test\n  (is (= #{} (dep/unique-cycles {})))\n  (is (= #{[2 2]} (dep/unique-cycles {2 #{2}})))\n  (is (= #{} (dep/unique-cycles {1 #{2}})))\n  (doseq [size [5 10]] ; gen cycles of these sizes (higher is very slow)\n    (let [[deps smallest-cycles] (gen-dep-cycle size)]\n      (doseq [c (maps-like 10 deps)] ; shuffle deps order <..> times\n        (let [actual (dep/unique-cycles c)]\n          (every? #(is (cycle-actually-occurs deps %)\n                       (str \"Cycle doesn't occur:\\n\"\n                            \"deps: \" deps \\newline\n                            \"claimed cycle: \" %))\n                  actual)\n          (is (seq (set/intersection smallest-cycles actual))\n              (str \"Missing smallest cycle(s) for size \" size \": \"\n                   (pr-str actual))))))))\n\n\n(defn check-cycle-error\n  [deps smlest-cycles]\n  (doseq [deps (maps-like 10 deps)]\n    (let [^Exception e (try (dep/topological-sort deps)\n                            (catch Exception e e))]\n      (is (instance? IExceptionInfo e)\n          (str \"Didn't throw an exception\\ndeps: \" deps))\n      (is (re-find #\"Dependency cycles? detected\" (.getMessage e)))\n      ;; pretty printed dependency cycle appears in msg\n      (is (->> e ex-data :cycles (some smlest-cycles))\n          (str \"Didn't include smallest cycle:\\n\"\n               \"deps: \" deps \"\\n\"\n               \"smallest-cycles: \" smlest-cycles \"\\n\"\n               \"actual cycles: \" (->> e ex-data :cycles) \"\\n\"\n               \"actual message: \" (.getMessage e))))))\n\n\n(deftest topological-sorting\n  (let [deps {:a #{}, :b #{:a}, :c #{:a :b} :x #{:b} :y #{:c}}]\n    (is (= [:a :b :c :x :y] (dep/topological-sort deps)))\n    (is (= [:b :c :x] (dep/topological-sort deps [:x :c :b]))))\n  (check-cycle-error {:a #{:b}, :b #{:c}, :c #{:a}}\n                     #{[:a :b :c :a]\n                       [:b :c :a :b]\n                       [:c :a :b :c]})\n  (doseq [size [5 10]] ; gen cycles of these sizes (higher is very slow)\n    (let [[deps smallest-cycles] (gen-dep-cycle size)]\n      (doseq [c (maps-like 5 deps)] ; shuffle deps order <..> times\n        (check-cycle-error c smallest-cycles)))))\n\n\n(deftest pretty-cycle-test\n  (are [c strs] (= (str/join \\newline strs) (dep/pretty-cycle c))\n    [1 1]\n    [\"+ 1\"\n     \"^\\\\\"\n     \"|_|\"]\n\n    [1 2 1]\n    [\"+ 1\"\n     \"^ + 2\"\n     \"|_/\"]\n\n    [1 2 3 1]\n    [\"+ 1\"\n     \"^ + 2\"\n     \"|  + 3\"\n     \"|_/\"]\n\n    [1 2 3 4 1]\n    [\"+ 1\"\n     \"^ + 2\"\n     \"|  + 3\"\n     \"|   + 4\"\n     \"|__/\"]\n\n    [1 2 3 4 5 1]\n    [\"+ 1\"\n     \"^ + 2\"\n     \"|  + 3\"\n     \"|   + 4\"\n     \"|    + 5\"\n     \"|___/\"]))\n"
  },
  {
    "path": "test/lein_monolith/monolith_test.clj",
    "content": "(ns lein-monolith.monolith-test\n  (:require\n    [clojure.java.io :as io]\n    [clojure.test :refer [deftest is]]\n    [lein-monolith.test-utils :refer [use-example-project read-example-project]]\n    [leiningen.monolith :as monolith]))\n\n\n(use-example-project)\n\n\n(defn- absolute-path\n  \"Return an absolute java.nio.file.Path for the given file-ish input.\"\n  [x]\n  (.. (io/as-file x) toPath toAbsolutePath))\n\n\n(defn- read-pprint-output\n  \"Runs lein pprint with the given key path using the :monolith/all profile.\"\n  [& ks]\n  (->> (map str ks)\n       (apply vector \"pprint\")\n       (monolith/with-all (read-example-project))\n       with-out-str\n       read-string))\n\n\n(defn- relativize-path\n  \"Convert absolute paths to paths relative to the example project.\"\n  [path]\n  (str (.relativize (absolute-path \"example\") (absolute-path path))))\n\n\n(defn- relativize-pprint-output\n  \"Read pprint output and convert absolute paths to paths relative to the\n  example project.\"\n  [& ks]\n  (->> ks\n       (apply read-pprint-output)\n       (map relativize-path)))\n\n\n(deftest with-all-test\n  (is (= [\"apps/app-a/resources\"\n          \"dev-resources\"\n          \"libs/lib-a/resources\"\n          \"libs/lib-b/resources\"\n          \"libs/lib-d/resources\"\n          \"libs/subdir/lib-c/resources\"\n          \"resources\"]\n         (relativize-pprint-output :resource-paths)))\n\n  (is (= [\"apps/app-a/src\"\n          \"libs/lib-a/src\"\n          \"libs/lib-b/src\"\n          \"libs/lib-d/src\"\n          \"libs/subdir/lib-c/src\"\n          \"src\"]\n         (relativize-pprint-output :source-paths)))\n\n  (is (= [\"apps/app-a/test/integration\"\n          \"apps/app-a/test/unit\"\n          \"libs/lib-a/test/integration\"\n          \"libs/lib-a/test/unit\"\n          \"libs/lib-b/test/integration\"\n          \"libs/lib-b/test/unit\"\n          \"libs/lib-d/test/integration\"\n          \"libs/lib-d/test/unit\"\n          \"libs/subdir/lib-c/test/integration\"\n          \"libs/subdir/lib-c/test/unit\"\n          \"test/integration\"\n          \"test/unit\"]\n         (relativize-pprint-output :test-paths))))\n"
  },
  {
    "path": "test/lein_monolith/plugin_test.clj",
    "content": "(ns lein-monolith.plugin-test\n  (:require\n    [clojure.test :refer [deftest is]]\n    [lein-monolith.config :as config]\n    [lein-monolith.plugin :as plugin]\n    [lein-monolith.test-utils :refer [use-example-project read-example-project]]\n    [leiningen.core.project :as project]))\n\n\n(use-example-project)\n\n\n(deftest build-inherited-profiles-test\n  (let [monolith (config/find-monolith! (read-example-project))\n        subproject (project/read \"example/apps/app-a/project.clj\")\n        profiles (into {} (plugin/build-inherited-profiles monolith subproject))]\n    (is (= #{:monolith/inherited\n             :monolith/inherited-raw\n             :monolith/leaky\n             :monolith/leaky-raw}\n           (set (keys profiles))))\n    (is (= {:test-paths [\"test/unit\" \"test/integration\"]}\n           (:monolith/inherited-raw profiles)))\n    (is (= {:repositories\n            [[\"central\"\n              {:url \"https://repo1.maven.org/maven2/\"\n               :snapshots false}]\n             [\"clojars\" {:url \"https://repo.clojars.org/\"}]]\n            :managed-dependencies [['amperity/greenlight \"0.6.0\"] ['com.amperity/vault-clj \"2.1.583\"]]}\n           (:monolith/leaky profiles)))\n    (is (= {:compile-path \"%s/compiled\"}\n           (:monolith/leaky-raw profiles)))))\n\n\n(deftest build-dependency-set-profile-test\n  (let [monolith (config/find-monolith! (read-example-project))\n        subproject (project/read \"example/apps/app-a/project.clj\")\n        profile (into {} (plugin/build-dependency-profiles monolith subproject))]\n    (is (= :set-a (:monolith/dependency-set subproject)))\n    (is (= [['amperity/greenlight \"0.7.1\"] ['org.clojure/spec.alpha \"0.3.218\"]]\n           (get-in monolith [:monolith :dependency-sets :set-a])))\n    (is (= [['amperity/greenlight \"0.7.1\"] ['org.clojure/spec.alpha \"0.3.218\"]]\n           (get-in profile [:monolith/dependency-set :managed-dependencies])))))\n\n\n(deftest managed-dependencies-order\n  (let [monolith (config/find-monolith! (read-example-project))\n        subproject (-> (project/read \"example/apps/app-a/project.clj\")\n                       (plugin/middleware monolith))]\n    (is (= [['amperity/greenlight \"0.7.1\"]\n            ['org.clojure/spec.alpha \"0.3.218\"]]\n           (:managed-dependencies subproject)))))\n"
  },
  {
    "path": "test/lein_monolith/task/each_test.clj",
    "content": "(ns lein-monolith.task.each-test\n  (:require\n    [clojure.java.io :as io]\n    [clojure.string :as str]\n    [clojure.test :refer [deftest is testing]]\n    [lein-monolith.config :as config]\n    [lein-monolith.task.each :as each]\n    [lein-monolith.test-utils :refer [read-example-project]]))\n\n\n(defn- test-path\n  \"Returns the path where the test file should exist for the given target path.\"\n  [subproject target]\n  (if (= :target-path target)\n    (str (:root subproject) \"/target/test.txt\")\n    (str target \"/test.txt\")))\n\n\n(deftest clean-subprojects\n  (testing \"Verify that the clean targets for each subproject are cleaned up by `lein monolith each clean`.\"\n    (let [monolith (read-example-project)\n          subprojects (config/read-subprojects! monolith)]\n      (doseq [[_subproject-name subproject] subprojects\n              target (:clean-targets subproject)]\n        (let [path (test-path subproject target)]\n          (is (str/starts-with? path (:root subproject))\n              \"The test file path should be created within the subproject directory.\")\n          (io/make-parents path)\n          (spit path \"test\")\n          (is (.exists (io/file path)) \"The test file should have been created.\")))\n      (each/run-tasks monolith {} [\"clean\"]) ; lein monolith each clean\n      (doseq [[_subproject-name subproject] subprojects\n              target (:clean-targets subproject)]\n        (let [path (test-path subproject target)\n              test-file (io/file path)\n              parent-dir (io/file (.getParent test-file))]\n          (is (not (.exists test-file)) \"The test file should not exist after a lein clean\")\n          (is (not (.exists parent-dir)) \"The target directory should not exist after a lein clean\"))))))\n"
  },
  {
    "path": "test/lein_monolith/task/util_test.clj",
    "content": "(ns lein-monolith.task.util-test\n  (:require\n    [clojure.test :refer [deftest testing is]]\n    [lein-monolith.task.util :as u]))\n\n\n(deftest shell-escaping\n  (is (= \"nil\" (u/shell-escape nil)))\n  (is (= \"123\" (u/shell-escape 123)))\n  (is (= \"foo\" (u/shell-escape \"foo\")))\n  (is (= \":abc\" (u/shell-escape :abc)))\n  (is (= \"'[123 true]'\" (u/shell-escape [123 true])))\n  (is (= \"'\\\\'foo'\" (u/shell-escape \"'foo\")))\n  (is (= \"'\\\"xyz\\\"'\" (u/shell-escape \"\\\"xyz\\\"\"))))\n\n\n(deftest kw-arg-parsing\n  (testing \"empty arguments\"\n    (is (= [{} []]\n           (u/parse-kw-args {} [])))\n    (is (= [{} []]\n           (u/parse-kw-args {:foo 0} []))))\n  (testing \"flag options\"\n    (is (= [{:foo true} []]\n           (u/parse-kw-args {:foo 0} [\":foo\"])))\n    (is (= [{:foo true} [\"bar\"]]\n           (u/parse-kw-args {:foo 0} [\":foo\" \"bar\"]))))\n  (testing \"single-value options\"\n    (is (= [{:abc \"xyz\"} [\"123\"]]\n           (u/parse-kw-args {:abc 1} [\":abc\" \"xyz\" \"123\"])))\n    (is (= [{} [\"%\" \":abc\" \"123\"]]\n           (u/parse-kw-args {:abc 1} [\"%\" \":abc\" \"123\"]))))\n  (testing \"multi-arg options\"\n    (is (= [{:tri-arg [\"1\" \"2\" \"3\"]} [\"abc\"]]\n           (u/parse-kw-args {:tri-arg 3} [\":tri-arg\" \"1\" \"2\" \"3\" \"abc\"])))\n    (is (= [{:missing [\"abc\"]} []]\n           (u/parse-kw-args {:missing 2} [\":missing\" \"abc\"])))\n    (is (= [{:foo [\"x\" \"y\"]} [\"bar\"]]\n           (u/parse-kw-args {:foo 2} [\":foo\" \"a\" \"b\" \":foo\" \"x\" \"y\" \"bar\"]))\n        \"should overwrite prior option\"))\n  (testing \"multi-value options\"\n    (is (= [{:foo [\"a\"]} []]\n           (u/parse-kw-args {:foo* 1} [\":foo\" \"a\"])))\n    (is (= [{:foo [\"a\" \"b\"], :bar true} []]\n           (u/parse-kw-args {:foo* 1, :bar 0} [\":foo\" \"a\" \":bar\" \":foo\" \"b\"]))))\n  (testing \"combo args\"\n    (is (= [{:foo \"1\", :bar true} [\"xyz\"]]\n           (u/parse-kw-args {:foo 1, :bar 0} [\":foo\" \"1\" \":bar\" \"xyz\"])))\n    (is (= [{:foo \"x\"} [\":bar\" \"123\" \":foo\" \"y\"]]\n           (u/parse-kw-args {:foo 1} [\":foo\" \"x\" \":bar\" \"123\" \":foo\" \"y\"]))\n        \"unknown arg should halt parsing\")))\n"
  },
  {
    "path": "test/lein_monolith/task/with_dependency_set_test.clj",
    "content": "(ns lein-monolith.task.with-dependency-set-test\n  (:require\n    [clojure.test :refer [deftest testing is]]\n    [lein-monolith.task.with-dependency-set :as wds]\n    [lein-monolith.test-utils :refer [use-example-project]]\n    [leiningen.core.main :as lein]\n    [leiningen.core.project :as project]\n    [leiningen.monolith :as monolith]))\n\n\n(use-example-project)\n\n\n(deftest run-task-test\n  (with-redefs [lein/resolve-and-apply (fn [project & _] project)]\n    (testing \"Root Project\"\n      (let [project (project/read \"example/project.clj\")\n            deps [['amperity/greenlight \"0.7.1\"]\n                  ['org.clojure/spec.alpha \"0.3.218\"]]\n            actual (wds/run-task project :set-a nil)]\n        (is (= deps (:managed-dependencies actual)))\n        (is (= deps\n               (get-in actual [:profiles :monolith/dependency-override :managed-dependencies])))))\n    (testing \"Subproject\"\n      (let [project (project/read \"example/apps/app-a/project.clj\")\n            replaced-deps [['amperity/greenlight \"0.7.0\"]\n                           ['org.clojure/spec.alpha \"0.2.194\"]]\n            actual (wds/run-task project :set-outdated nil)]\n        (is (= replaced-deps (:managed-dependencies actual)))\n        (is (= replaced-deps\n               (get-in actual [:profiles :monolith/dependency-override :managed-dependencies])))\n        (is (= :set-outdated (:monolith/dependency-set actual)))))\n    (testing \"Unknown dependency set\"\n      (let [project (project/read \"example/project.clj\")]\n        (is (thrown? Exception (wds/run-task project :unknown nil)))))))\n\n\n(deftest monolith-task-test\n  (testing \"Parent subtask throws\"\n    (let [project (project/read \"example/project.clj\")]\n      (is (thrown? Exception (monolith/monolith project\n                                                \"with-dependency-set\"\n                                                [\":foo\" \"monolith\" \"each\" \"clean\"]))))))\n"
  },
  {
    "path": "test/lein_monolith/test_utils.clj",
    "content": "(ns lein-monolith.test-utils\n  (:require\n    [clojure.test :refer [use-fixtures]]\n    [lein-monolith.task.each :as each]\n    [lein-monolith.task.util :as u]\n    [leiningen.core.main :as lein]\n    [leiningen.core.project :as project]\n    [leiningen.deps :as deps]\n    [leiningen.install :as install])\n  (:import\n    (java.io\n      StringWriter)))\n\n\n(defn read-example-project\n  \"Read in the example monolith project.\"\n  []\n  (project/read \"example/project.clj\"))\n\n\n(defn prepare-example-project\n  \"Prepare the example project by installing the source version of\n  lein-monolith, fetching the example project's dependencies, and installing all\n  of the example project's subprojects.\"\n  []\n  (let [out (StringWriter.)]\n    (try\n      (binding [lein/*exit-process?* false\n                *out* out\n                *err* out]\n        (install/install (project/read \"project.clj\"))\n        (let [[monolith subprojects] (u/load-monolith! (read-example-project))]\n          (deps/deps monolith)\n          (doseq [[_project-name project] subprojects]\n            (each/run-tasks project {} [\"install\"]))))\n      (catch Exception e\n        (.println *err* (str out))\n        (throw e)))))\n\n\n(defn use-example-project\n  \"Adds a fixture that ensures that the example project is completely set up so\n  monolith tasks can be run against it for testing.\"\n  []\n  (use-fixtures :once (fn [f]\n                        (prepare-example-project)\n                        (f))))\n"
  }
]