Repository: benedekfazekas/mranderson
Branch: master
Commit: baa428bef2f0
Files: 37
Total size: 131.1 KB
Directory structure:
gitextract_t9wpat55/
├── .clj-kondo/
│ ├── config.edn
│ └── rewrite-clj/
│ └── rewrite-clj/
│ └── config.edn
├── .codecov.yml
├── .github/
│ └── workflows/
│ └── ci.yml
├── .gitignore
├── .gitmodules
├── CHANGELOG.md
├── LICENSE
├── Makefile
├── README.md
├── README_old.md
├── build.sh
├── gargamel.edn
├── java-src/
│ └── mranderson/
│ └── util/
│ ├── JjMainProcessor.java
│ ├── JjPackageRemapper.java
│ └── JjWildcard.java
├── md-templates/
│ ├── changelog.mustache
│ └── commit.mustache
├── project.clj
├── scripts/
│ └── integration_test.sh
├── src/
│ ├── leiningen/
│ │ └── inline_deps.clj
│ └── mranderson/
│ ├── core.clj
│ ├── dependency/
│ │ ├── resolver.clj
│ │ └── tree.clj
│ ├── log.clj
│ ├── move.clj
│ ├── plugin.clj
│ ├── profiles.clj
│ └── util.clj
├── test/
│ └── mranderson/
│ ├── core_test.clj
│ ├── dependency_resolver_test.clj
│ ├── move_test.clj
│ ├── test.clj
│ └── util_test.clj
├── test-resources/
│ ├── a/
│ │ └── same-name/
│ │ └── f
│ └── b/
│ └── same-name/
│ └── g
└── tests.edn
================================================
FILE CONTENTS
================================================
================================================
FILE: .clj-kondo/config.edn
================================================
{:config-paths ^:replace [] ;; don't pick up user defaults
:lint-as
{mranderson.test/with-mranderson clojure.core/let
mranderson.test/with-project clojure.core/let}
:linters
{:unused-binding {:exclude-destructured-keys-in-fn-args true}
:cond-else {:level :off}}}
================================================
FILE: .clj-kondo/rewrite-clj/rewrite-clj/config.edn
================================================
{:lint-as
{rewrite-clj.zip/subedit-> clojure.core/->
rewrite-clj.zip/subedit->> clojure.core/->>
rewrite-clj.zip/edit-> clojure.core/->
rewrite-clj.zip/edit->> clojure.core/->>}}
================================================
FILE: .codecov.yml
================================================
coverage:
status:
project:
default:
informational: true
patch:
default:
informational: true
comment: false
================================================
FILE: .github/workflows/ci.yml
================================================
name: CI
on:
push:
branches: [master]
pull_request:
branches: [master]
jobs:
build:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
jdk: [8, 11, 17]
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Set up JDK ${{ matrix.jdk }}
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: ${{ matrix.jdk }}
- name: Install Leiningen
uses: DeLaGuardo/setup-clojure@13.4
with:
lein: latest
- name: Cache Maven dependencies
uses: actions/cache@v4
with:
path: ~/.m2/repository
key: m2-${{ hashFiles('project.clj') }}
restore-keys: m2-
- name: Install dependencies
run: lein deps
- name: Run tests
run: lein test
- name: Shellcheck
run: shellcheck scripts/*.sh
- name: Integration tests
continue-on-error: true
run: scripts/integration_test.sh
- name: Lint
run: lein with-profile +eastwood eastwood
- name: Coverage
run: lein kaocha-coverage --codecov
- name: Upload coverage
if: matrix.jdk == 11
uses: codecov/codecov-action@v5
with:
files: target/coverage/codecov.json
token: ${{ secrets.CODECOV_TOKEN }}
================================================
FILE: .gitignore
================================================
/target
/classes
/checkouts
/test-resources/c
pom.xml
pom.xml.asc
*.jar
*.class
/.lein-*
/.nrepl-port
*~
/.clj-kondo/.cache
/.lsp
/.inline
/.eastwood
================================================
FILE: .gitmodules
================================================
[submodule "test-resources/cider-nrepl"]
path = test-resources/cider-nrepl
url = https://github.com/clojure-emacs/cider-nrepl.git
[submodule "test-resources/refactor-nrepl"]
path = test-resources/refactor-nrepl
url = https://github.com/clojure-emacs/refactor-nrepl.git
================================================
FILE: CHANGELOG.md
================================================
# Changelog
## Unreleased
## Bug fixes
- [fix [#78](https://github.com/benedekfazekas/mranderson/issues/78)] Fix watermarking moved namespaces
- [fix [#53](https://github.com/benedekfazekas/mranderson/issues/53)] Fix inlining where one directory name in the library is a substring of an other directory name
- [fix [#49](https://github.com/benedekfazekas/mranderson/issues/49)] Options accepted from both CLI and the project map
- [fix [#72](https://github.com/benedekfazekas/mranderson/issues/72)] Mranderson rewrites some clj/cljc files twice (instaparse)
- [fix [#56](https://github.com/benedekfazekas/mranderson/issues/56)] Fix intermittent broken files when inlining
- [fix [#90](https://github.com/benedekfazekas/mranderson/issues/90)] Fix spade/core.cljs not found
- [fix [#76](https://github.com/benedekfazekas/mranderson/issues/76)] Consider `org.clojure/core.rrb-vector` as part of clojure.core
## Changes
- [feature [#42](https://github.com/benedekfazekas/mranderson/issues/42)] uncouple MrAnderson from leiningen to support general use
- [maint [#66](https://github.com/benedekfazekas/mranderson/issues/66)] bump MrAnderson dependencies
- [fix [#63](https://github.com/benedekfazekas/mranderson/pull/63)] introduce `mranderson.internal.no-parallelism` as on option temporarily
- integration tests against `cider-nrepl` and `refactor-nrepl`
- improve CI matrix
- Simplify internal threading setup
- [fix [#58](https://github.com/benedekfazekas/mranderson/issues/58)] Offer a new `:included-source-paths` option, which is described in the README.
- use `org.pantsbuild.jarjar:1.7.2` instead of `jarjar:1.3`. Also abandoned but still an upgrade
- exclude `clj-kondo.exports` from jar extraction by @bbatsov
- migrate from cricleci to github actions by @bbatsov
- dependency upgrades by @bbatsov
- fix minor code issues by @bbatsov
- Update cider-nrepl submodule and mark integration tests as soft failures by @bbatsov
## 0.5.3
### Bug fixes
- [fix [#47](https://github.com/benedekfazekas/mranderson/issues/47)] Error when inlining cljfmt 0.7
## 0.5.2
### Bug fixes
- [Fix [#44](https://github.com/benedekfazekas/mranderson/issues/44)] Ignore invalid duplicates where file location does not match namespace declaration -- thanks @xsc
- Fix NPE when trying to process cljc file without a namespace declaration
### Changes
- minimum leiningen version upgraded to 2.9.1
- some internal improvements: tests added, kaocha added as test runner, codecov added to CI
## 0.5.1
### Bug fixes
- [Fix [#29](https://github.com/benedekfazekas/mranderson/issues/29)] Only consider sym a libspec-prefix if it is at the beginning of a list
- [Fix [#28](https://github.com/benedekfazekas/mranderson/issues/28)] Fully qualified sym can be a dotted prefix to refer a `def` in cljs
- [Fix [#14](https://github.com/benedekfazekas/mranderson/issues/14)] Teach mranderson about imports where the namespace name prefixes the record name with a dot, for exampe `(:import foo.bar.baz.FooBar)`
### Changes
- [Fix [#31](https://github.com/benedekfazekas/mranderson/issues/31)] Do not copy anything under `META-INF` directory when unzipping dependency for inlining as meta files are unnecessary in the end product.
- [Fix [#30](https://github.com/benedekfazekas/mranderson/issues/30)] Both `:inline-dep` and `:source-dep` meta tag on dependencies are supported to signal that MrAnderson should inline given dependency.
- Related to [#14](https://github.com/benedekfazekas/mranderson/issues/14) when the ns macro is processed the `:import` section of the ns macro is processed first. Only when that part is done the rest of the ns macro is considered for replacements.
## 0.5.0
### Breaking changes
- `source-deps` leiningen task is renamed to `inline-deps`
### Changes
- introducing **unresolved tree** mode where mranderson works on an unresolved dependency tree, walks it depth first when processing it. applies transient deps hygiene
- introduce **resolved tree** mode where resolved dependency tree is flattened into a topological ordered list for processing. every depedency (transient ones too) is considered first level
- namespaces are split when processed: the ns macro is parsed and modified with `rewrite-clj` the body of the ns is processed textually
- process files in parallel when updating changed namespace in all dependent files ([parallel](https://github.com/reborg/parallel) is used)
- add some tests and auto build via circleci
- breaking up `mranderson.source-deps` ns into multiple namespaces for readability/maintainablity
- mranderson understands `:mranderson` section in the project.clj for certain config options (`project-prefix`, `overrides`, `expositions`, `shadowing-only`)
- add `overrides` config option for the **unresolved tree** mode to make overriding transient deps possible
- add `expositions` config option for the **unresolved tree** mode to override transient deps hygiene so a transient dependency would be available for the project's own source files
- track changes in a this file from this release instead of the github releases page
### Bug fixes
- [Fix [#7](https://github.com/benedekfazekas/mranderson/issues/7)] Both **unresolved tree** and **resolved tree** modes fix overriden deps break topological sort issue. The topological order is not important when the unresolved tree is used whilest the topological order is derrived from the unresolved tree when resolved tree is used in **resolved tree** mode
- [Fix [#19](https://github.com/benedekfazekas/mranderson/issues/19)] Add watermark to mrandersoned dependency nses
- [Fix [#21](https://github.com/benedekfazekas/mranderson/issues/21)] Rewrite README to make it more informative (hopefully)
- [Fix [#25](https://github.com/benedekfazekas/mranderson/issues/25)] project-prefix's default is unique so mrandersoned libs using the same mranderson version can't clash
## Previous versions
Please see the [releases](https://github.com/benedekfazekas/mranderson/releases) page on github.
================================================
FILE: LICENSE
================================================
THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC
LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM
CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
1. DEFINITIONS
"Contribution" means:
a) in the case of the initial Contributor, the initial code and
documentation distributed under this Agreement, and
b) in the case of each subsequent Contributor:
i) changes to the Program, and
ii) additions to the Program;
where such changes and/or additions to the Program originate from and are
distributed by that particular Contributor. A Contribution 'originates' from
a Contributor if it was added to the Program by such Contributor itself or
anyone acting on such Contributor's behalf. Contributions do not include
additions to the Program which: (i) are separate modules of software
distributed in conjunction with the Program under their own license
agreement, and (ii) are not derivative works of the Program.
"Contributor" means any person or entity that distributes the Program.
"Licensed Patents" mean patent claims licensable by a Contributor which are
necessarily infringed by the use or sale of its Contribution alone or when
combined with the Program.
"Program" means the Contributions distributed in accordance with this
Agreement.
"Recipient" means anyone who receives the Program under this Agreement,
including all Contributors.
2. GRANT OF RIGHTS
a) Subject to the terms of this Agreement, each Contributor hereby grants
Recipient a non-exclusive, worldwide, royalty-free copyright license to
reproduce, prepare derivative works of, publicly display, publicly perform,
distribute and sublicense the Contribution of such Contributor, if any, and
such derivative works, in source code and object code form.
b) Subject to the terms of this Agreement, each Contributor hereby grants
Recipient a non-exclusive, worldwide, royalty-free patent license under
Licensed Patents to make, use, sell, offer to sell, import and otherwise
transfer the Contribution of such Contributor, if any, in source code and
object code form. This patent license shall apply to the combination of the
Contribution and the Program if, at the time the Contribution is added by the
Contributor, such addition of the Contribution causes such combination to be
covered by the Licensed Patents. The patent license shall not apply to any
other combinations which include the Contribution. No hardware per se is
licensed hereunder.
c) Recipient understands that although each Contributor grants the licenses
to its Contributions set forth herein, no assurances are provided by any
Contributor that the Program does not infringe the patent or other
intellectual property rights of any other entity. Each Contributor disclaims
any liability to Recipient for claims brought by any other entity based on
infringement of intellectual property rights or otherwise. As a condition to
exercising the rights and licenses granted hereunder, each Recipient hereby
assumes sole responsibility to secure any other intellectual property rights
needed, if any. For example, if a third party patent license is required to
allow Recipient to distribute the Program, it is Recipient's responsibility
to acquire that license before distributing the Program.
d) Each Contributor represents that to its knowledge it has sufficient
copyright rights in its Contribution, if any, to grant the copyright license
set forth in this Agreement.
3. REQUIREMENTS
A Contributor may choose to distribute the Program in object code form under
its own license agreement, provided that:
a) it complies with the terms and conditions of this Agreement; and
b) its license agreement:
i) effectively disclaims on behalf of all Contributors all warranties and
conditions, express and implied, including warranties or conditions of title
and non-infringement, and implied warranties or conditions of merchantability
and fitness for a particular purpose;
ii) effectively excludes on behalf of all Contributors all liability for
damages, including direct, indirect, special, incidental and consequential
damages, such as lost profits;
iii) states that any provisions which differ from this Agreement are offered
by that Contributor alone and not by any other party; and
iv) states that source code for the Program is available from such
Contributor, and informs licensees how to obtain it in a reasonable manner on
or through a medium customarily used for software exchange.
When the Program is made available in source code form:
a) it must be made available under this Agreement; and
b) a copy of this Agreement must be included with each copy of the Program.
Contributors may not remove or alter any copyright notices contained within
the Program.
Each Contributor must identify itself as the originator of its Contribution,
if any, in a manner that reasonably allows subsequent Recipients to identify
the originator of the Contribution.
4. COMMERCIAL DISTRIBUTION
Commercial distributors of software may accept certain responsibilities with
respect to end users, business partners and the like. While this license is
intended to facilitate the commercial use of the Program, the Contributor who
includes the Program in a commercial product offering should do so in a
manner which does not create potential liability for other Contributors.
Therefore, if a Contributor includes the Program in a commercial product
offering, such Contributor ("Commercial Contributor") hereby agrees to defend
and indemnify every other Contributor ("Indemnified Contributor") against any
losses, damages and costs (collectively "Losses") arising from claims,
lawsuits and other legal actions brought by a third party against the
Indemnified Contributor to the extent caused by the acts or omissions of such
Commercial Contributor in connection with its distribution of the Program in
a commercial product offering. The obligations in this section do not apply
to any claims or Losses relating to any actual or alleged intellectual
property infringement. In order to qualify, an Indemnified Contributor must:
a) promptly notify the Commercial Contributor in writing of such claim, and
b) allow the Commercial Contributor tocontrol, and cooperate with the
Commercial Contributor in, the defense and any related settlement
negotiations. The Indemnified Contributor may participate in any such claim
at its own expense.
For example, a Contributor might include the Program in a commercial product
offering, Product X. That Contributor is then a Commercial Contributor. If
that Commercial Contributor then makes performance claims, or offers
warranties related to Product X, those performance claims and warranties are
such Commercial Contributor's responsibility alone. Under this section, the
Commercial Contributor would have to defend claims against the other
Contributors related to those performance claims and warranties, and if a
court requires any other Contributor to pay any damages as a result, the
Commercial Contributor must pay those damages.
5. NO WARRANTY
EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON
AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER
EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR
CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A
PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the
appropriateness of using and distributing the Program and assumes all risks
associated with its exercise of rights under this Agreement , including but
not limited to the risks and costs of program errors, compliance with
applicable laws, damage to or loss of data, programs or equipment, and
unavailability or interruption of operations.
6. DISCLAIMER OF LIABILITY
EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY
CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION
LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE
EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY
OF SUCH DAMAGES.
7. GENERAL
If any provision of this Agreement is invalid or unenforceable under
applicable law, it shall not affect the validity or enforceability of the
remainder of the terms of this Agreement, and without further action by the
parties hereto, such provision shall be reformed to the minimum extent
necessary to make such provision valid and enforceable.
If Recipient institutes patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Program itself
(excluding combinations of the Program with other software or hardware)
infringes such Recipient's patent(s), then such Recipient's rights granted
under Section 2(b) shall terminate as of the date such litigation is filed.
All Recipient's rights under this Agreement shall terminate if it fails to
comply with any of the material terms or conditions of this Agreement and
does not cure such failure in a reasonable period of time after becoming
aware of such noncompliance. If all Recipient's rights under this Agreement
terminate, Recipient agrees to cease use and distribution of the Program as
soon as reasonably practicable. However, Recipient's obligations under this
Agreement and any licenses granted by Recipient relating to the Program shall
continue and survive.
Everyone is permitted to copy and distribute copies of this Agreement, but in
order to avoid inconsistency the Agreement is copyrighted and may only be
modified in the following manner. The Agreement Steward reserves the right to
publish new versions (including revisions) of this Agreement from time to
time. No one other than the Agreement Steward has the right to modify this
Agreement. The Eclipse Foundation is the initial Agreement Steward. The
Eclipse Foundation may assign the responsibility to serve as the Agreement
Steward to a suitable separate entity. Each new version of the Agreement will
be given a distinguishing version number. The Program (including
Contributions) may always be distributed subject to the version of the
Agreement under which it was received. In addition, after a new version of
the Agreement is published, Contributor may elect to distribute the Program
(including its Contributions) under the new version. Except as expressly
stated in Sections 2(a) and 2(b) above, Recipient receives no rights or
licenses to the intellectual property of any Contributor under this
Agreement, whether expressly, by implication, estoppel or otherwise. All
rights in the Program not expressly granted under this Agreement are
reserved.
This Agreement is governed by the laws of the State of Washington and the
intellectual property laws of the United States of America. No party to this
Agreement will bring a legal action under this Agreement more than one year
after the cause of action arose. Each party waives its rights to a jury trial
in any resulting litigation.
================================================
FILE: Makefile
================================================
.PHONY: install inline test integration-test deploy clean
# Note that `install` is a two-step process: given that mranderson depends on itself as a plugin,
# it first needs to be installed without the plugin, for bootstrapping this self dependency.
install:
lein clean
lein with-profile -user,-dev install
lein with-profile -user,-dev,+mranderson-plugin install
.inline: install
rm target/*.jar
rm pom.xml
lein with-profile -user,-dev,+mranderson-plugin inline-deps :skip-javaclass-repackage true
touch .inline
inline: .inline
test:
lein test
integration-test:
scripts/integration_test.sh
deploy: .inline
lein with-profile -user,-dev,+mranderson-profile deploy clojars
clean:
lein clean
rm -rf .inline
================================================
FILE: README.md
================================================
[](https://github.com/benedekfazekas/mranderson/actions/workflows/ci.yml)
[](https://codecov.io/gh/benedekfazekas/mranderson)
[](https://clojars.org/thomasa/mranderson)
[](https://cljdoc.org/d/thomasa/mranderson/CURRENT)
[](https://clojurians.slack.com/archives/C04HWRD2D32)
# MrAnderson
MrAnderson is a dependency inlining and shadowing tool. It isolates the project's dependencies so they can not interfere with other libraries' dependencies.
## Is it good? Should I use it?
Yes and yes.
Use it if you have dependency conflicts and don't care to solve them. In **unresolved tree** mode MrAnderson creates deeply nested, local dependencies where any subtree is isolated from the rest of the tree therefore the same library can occur several times without conflicting. Or use it if you don't want your library's dependencies to interfere with the dependencies of your users' (leiningen plugins is a good example here). Or if you want to explore a bit more in the hellish land of dependency handling.
## Usage
MrAnderson is a leiningen plugin. Put `[thomasa/mranderson "0.5.3"]` into the `:plugins` vector of your project.clj. You can also use it directly not as a leiningen plugin, see [conjure-deps](https://github.com/Olical/conjure-deps) for an example.
Mark some of the dependencies in your dependencies vector in the project's `project.clj` with `^:inline-dep` meta tag. For example:
```clojure
:dependencies [[org.clojure/clojure "1.5.1"]
^:inline-dep [org.clojure/tools.namespace "0.2.5"]
^:inline-dep [org.clojure/tools.reader "0.8.5"]
^:inline-dep [org.clojure/tools.nrepl "0.2.3"]]
```
Only the marked dependencies will be considered by MrAnderson. (Both `:inline-dep` and `:source-dep` meta tags are supported.)
Then run
$ lein inline-deps
This retrieves and modifies the marked dependencies and copies them to `target/srcdeps` together with the modified project files -- their references to the dependencies need to change too.
After this you can start the REPL in the context of your inlined dependencies
$ lein with-profile +plugin.mranderson/config repl
Or run your tests with them
$ lein with-profile +plugin.mranderson/config test
Release locally
$ lein with-profile plugin.mranderson/config install
Release to clojars
$ lein with-profile +plugin.mranderson/config deploy clojars
Alternatively the modified dependencies and project files can be copied back to the source tree and stored in version control. In this case you don't need the above mentioned built in leiningen profile.
## Config and options
### Two modes: resolved tree and unresolved tree
MrAnderson has **two modes**. It can either work on an **unresolved** dependency **tree** and create a deeply nested directory structure for the unresolved tree based on the marked dependencies or only shadow a list of dependencies based on a **resolved dependency** tree of the same dependencies. The latter is the default.
In **unresolved tree** mode the same library -- even the same version of the library -- can occur multiple times in the unresolved dependency tree. When processing the tree MrAnderson walks it in a depth first order and creates a deeply nested directory structure and prefixes the namespaces and the references to them according to this directory structure.
Let's see [cider-nrepl](https://github.com/clojure-emacs/cider-nrepl)'s list of dependencies in the project file (as it is at the time of writing this README):
```clojure
:dependencies [[nrepl "0.6.0"]
^:source-dep [cider/orchard "0.4.0"]
^:source-dep [thunknyc/profile "0.5.2"]
^:source-dep [mvxcvi/puget "1.1.0"]
^:source-dep [fipp "0.6.15"]
^:source-dep [compliment "0.3.8"]
^:source-dep [cljs-tooling "0.3.1"]
^:source-dep [cljfmt "0.6.4" :exclusions [org.clojure/clojurescript]]
^:source-dep [org.clojure/tools.namespace "0.3.0-alpha4"]
^:source-dep [org.clojure/tools.trace "0.7.10"]
^:source-dep [org.clojure/tools.reader "1.2.2"]]
```
And the unresolved tree based on this list of dependencies for reference:
```
[cljs-tooling "0.3.1"]
[compliment "0.3.8"]
[fipp "0.6.15"]
[org.clojure/core.rrb-vector "0.0.13"]
[org.clojure/tools.trace "0.7.10"]
[cider/orchard "0.4.0"]
[org.clojure/java.classpath "0.3.0"]
[org.clojure/tools.namespace "0.3.0-alpha4"]
[org.clojure/java.classpath "0.2.3"]
[org.clojure/tools.reader "0.10.0"]
[org.tcrawley/dynapath "0.2.5"]
[cljfmt "0.6.4"]
[com.googlecode.java-diff-utils/diffutils "1.3.0"]
[org.clojure/tools.cli "0.3.7"]
[org.clojure/tools.reader "1.2.2"]
[rewrite-clj "0.6.0"]
[org.clojure/tools.reader "0.10.0"]
[rewrite-cljs "0.4.4"]
[org.clojure/tools.reader "1.0.5"]
[mvxcvi/puget "1.1.0"]
[fipp "0.6.14"]
[org.clojure/core.rrb-vector "0.0.13"]
[mvxcvi/arrangement "1.1.1"]
[org.clojure/tools.namespace "0.3.0-alpha4"]
[org.clojure/java.classpath "0.2.3"]
[org.clojure/tools.reader "0.10.0"]
[thunknyc/profile "0.5.2"]
[org.clojure/tools.reader "1.2.2"]
```
An example namespace of `[org.clojure/tools.reader "0.10.0"]` dependency of `[rewrite-clj "0.6.0"]` that is a dependency of `[cljfmt "0.6.4"]` will be prefixed like this:
```clojure
(ns ^{:mranderson/inlined true} cider.inlined-deps.cljfmt.v0v6v4.rewrite-clj.v0v6v0.toolsreader.v0v10v0.clojure.tools.reader.edn)
```
and a reference to it in `cider.inlined-deps.cljfmt.v0v6v4.rewrite-clj.v0v6v0.rewrite-clj.reader` like this:
```clojure
(:require [cider.inlined-deps.cljfmt.v0v6v4.rewrite-clj.v0v6v0.toolsreader.v0v10v0.clojure.tools.reader
[edn :as edn])
```
In the **resolved tree** mode MrAnderson flattens the resolved dependency tree out into a topoligically ordered list and processes this ordered list. While processing MrAnderson prefixes all namespaces in the dependencies and the references to them. This also means that all dependencies even transient ones are handled as first level dependencies as they can only occur once in a resolved dependency tree.
And the resolved tree of the same project:
```
[cljs-tooling "0.3.1"]
[compliment "0.3.8"]
[fipp "0.6.15"]
[org.clojure/core.rrb-vector "0.0.13"]
[org.clojure/tools.trace "0.7.10"]
[cider/orchard "0.4.0"]
[org.clojure/java.classpath "0.3.0"]
[org.tcrawley/dynapath "0.2.5"]
[cljfmt "0.6.4"]
[com.googlecode.java-diff-utils/diffutils "1.3.0"]
[org.clojure/tools.cli "0.3.7"]
[rewrite-clj "0.6.0"]
[rewrite-cljs "0.4.4"]
[mvxcvi/puget "1.1.0"]
[mvxcvi/arrangement "1.1.1"]
[org.clojure/tools.namespace "0.3.0-alpha4"]
[thunknyc/profile "0.5.2"]
[org.clojure/tools.reader "1.2.2"]
```
The same namespace from `tools.reader` -- note that there is only one version of it available in the dependency tree `1.2.2`:
```clojure
(ns ^{:mranderson/inlined true} cider.inlined-deps.toolsreader.v1v2v2.clojure.tools.reader.edn)
```
and a reference to it in `cider.inlined-deps.rewrite-clj.v0v6v0.rewrite-clj.reader` looks like this:
```clojure
(:require [cider.inlined-deps.toolsreader.v1v2v2.clojure.tools.reader
[edn :as edn])
```
In the **unresolved tree** mode the usual way of overriding dependencies, eg. putting a first level dependency in the project file with a newer version of a library does not work. Also in this mode MrAnderson applies transient dependency hygiene meaning that it does not search and replace occurrances of a transient depedency namespace in the project's own files. To work around these limitations you can create a MrAnderson specific section in the project file and define overrides as such:
```clojure
:mranderson {:overrides {[mvxcvi/puget fipp] [fipp "0.6.15"]}}
```
Note that the key in the overrides map is a path to a dependency in the unresolved dependency tree and the value is the new depedency node.
In the same section you can instruct MrAnderson to expose certain transient dependencies to the project's own source files as such:
```clojure
:mranderson {:expositions [[mvxcvi/puget fipp]]}
```
Here you have to provide a list of paths to dependencies to be exposed.
To use the **unresolved tree** mode you can either provide a flag in the above mentioned section
```clojure
:mranderson {:unresolved-tree true}
```
or you can provide the same flag on the command line:
$ lein inline-deps :unresolved-tree true
The latter supersedes the former.
Again: in the **resolved tree** mode no transient dependency hygiene is applied. Also the above described config options (*overrides* and *expositions*) don't take effect.
### Further config options
All the options can be provided via CLI or the project file.
| Option | Default | Description | Example |
|--------------------------|--------------------------------|-------------|---------|
| project-prefix | mranderson{rnd} | project pecific prefix to use when shadowing | `lein inline-deps :project-prefix cider.inlined-deps` |
| skip-javaclass-repackage | false | If true [Jar Jar Links](https://code.google.com/p/jarjar/) won't be used to repackage java classes in dependencies | `lein inline-deps :skip-javaclass-repackage true` |
| prefix-exclusions | empty list | List of prefixes which should not be processed in imports | `lein inline-deps :prefix-exclusions "[\"classlojure\"]"` |
| watermark | :mranderson/inlined | MrAnderson adds `watermark` as metadata to inlined namespaces. This allows tools like [cljdoc](https://cljdoc.org) to exclude inlined namespaces from a library's documented API. Cljdoc, for example, automatically excludes namespaces with any of `:mranderson/inlined`, `:no-doc`, `:skip-wiki` metadata. | `:mranderson {:watermark nil}` to switch off watermarking or provide your own keyword |
| unresolved-tree | false | Switch between **unresolved tree** and **resolved tree** mode | `lein inline-deps :unresolved-tree true` |
| overrides | empty list | Defines dependency overrides in **unresolved tree** mode | `:mranderson {:overrides {[mvxcvi/puget fipp] [fipp "0.6.15"]}}` |
| expositions | empty list | Makes transient dependencies available in the project's source files in **unresolved tree** mode | `:mranderson {:expositions [[mvxcvi/puget fipp]]}` |
| included-source-paths | nil | Determines which of the provided `:source-paths` (not `:test-paths`!) will be inlined. If `nil` or `:first`, the first source path (typically `"src"`) will be the only one to be processed. If set to `:source-paths`, all `:source-paths` will be processed. If set a vector, that vector will be interpreted as the list of source dirs to be processed, as-is, omitting the project `:source-dirs` value.
## Prerequisites
Leiningen 2.9.1 or above. For MrAnderson to work, does not mean your project is restricted to a java or clojure version.
### Supported OSes and platforms
MrAnderson is tested and supported on Linux and macOS. Windows systems are not supported or tested against.
### Projects that use MrAnderson
- [cider-nrepl](https://github.com/clojure-emacs/cider-nrepl)
- [refactor-nrepl](https://github.com/clojure-emacs/refactor-nrepl)
- [re-frame-10x](https://github.com/Day8/re-frame-10x)
- [iced-nrepl](https://github.com/liquidz/iced-nrepl)
- [conjure](https://github.com/Olical/conjure) via [conjure-deps](https://github.com/Olical/conjure-deps) -- uses MrAnderson directly, not as a `leiningen` plugin
## Related project
A really nice wrapper of mranderson can be found [here](https://github.com/xsc/lein-isolate).
## Credits
- The engine of namespace renaming/moving `mranderson.move` although heavily modified now is based on Stuart Sierra's `clojure.tools.namespace.move` namespace from [tools.namespace](https://github.com/clojure/tools.namespace).
- Some ideas around namespace renaming/moving was borrowed from @expez my co-maintainer for [refactor-nrepl](https://github.com/clojure-emacs/refactor-nrepl) in their fabolous work of `rename-file-or-dir` feature.
- @cichli did a round of profiling and perfromance/parallelisation fixes on mranderson which I took insipiration from
- Had amazing feedback, conversations around MrAnderson and dependencies with @bbatsov (MrAnderson's main client), @reborg, @SevereOverfl0w, @andrewmcveigh. Really grateful for the community and these nice people in particular.
## License
Copyright © 2014-2022 Benedek Fazekas
Distributed under the Eclipse Public License either version 1.0 or (at
your option) any later version.
================================================
FILE: README_old.md
================================================
[](https://circleci.com/gh/benedekfazekas/mranderson/tree/0.5.x)
# MrAnderson
Dependencies as source: used as if part of the project itself.
Somewhat node.js & npm style dependency handling as a leiningen plugin.
**Fancy words: 'npm style dependency handling' but what is this project is really about?**
It is an inlining tool which inlines your project's dependencies at packaging time. It automatically retrieves and prefixes your dependencies (both clojure source and java class files) and munges your clojure files -- mainly the namespace declaration but not only -- accordingly.
## Usage
Put `[thomasa/mranderson "0.4.9"]` into the `:plugins` vector of your project.clj.
Additionally you also need to mark some of the dependencies in your dependencies vector in the project's `project.clj` with `^:source-dep` meta tag. For example:
```clojure
:dependencies [[org.clojure/clojure "1.5.1"]
^:source-dep [org.clojure/tools.namespace "0.2.5"]
^:source-dep [org.clojure/tools.reader "0.8.5"]
^:source-dep [org.clojure/tools.nrepl "0.2.3"]]
```
Now you are ready to run:
$ lein source-deps
this retrieves dependencies and creates a deeply nested directory structure for them in `target/srcdeps` directory. It also munges all clojure source files accordingly. More over it uses [Jar Jar Links](https://code.google.com/p/jarjar/) to repackage your java class files dependencies if any.
If you don't want mranderson to repackage your java dependencies you can opt out by passing `:skip-javaclass-repackage true` as a parameter to `source-deps` task.
After that you can run your tests or your repl with:
$ lein with-profile +plugin.mranderson/config repl
$ lein with-profile +plugin.mranderson/config test
note the plus sign before the leiningen profile.
If you want to use mranderson while developing locally with the repl the source has to be modified in the target/srcdeps directory.
When you want to release locally:
$ lein with-profile plugin.mranderson/config install
to clojars:
$ lein with-profile +plugin.mranderson/config deploy clojars
If you want to change, update your dependencies just edit your `project.clj` file the usual way and run
$ lein clean
and then again
$ lein source-deps
and you are good to go.
**note** you should not mark clojure itself as a source dependency: there is a limit for everything.
## Related project
A really nice wrapper of mranderson can be found [here](https://github.com/xsc/lein-isolate).
## Under the hood
There is not much magic there but simple modifing the source files as strings [tools.namespace](https://github.com/clojure/tools.namespace) style; see specially `clojure.tools.namespace.move` namespace. Also additionally some more source file munging is done for prefixes, some deftypes and the like.
It also uses [Jar Jar Links](https://code.google.com/p/jarjar/) to repackage your java class files dependencies if any.
A bit of additional magic happens when you use the built in profile: it actively switches on the leiningen middleware also built into the plugin. The middleware AOT compiles some clojure sources and removes dependencies marked with `^:source-deps` from the dependency list. So they won't show up in the generated pom file and so on.
## Rationale
Some might argue that the clojure (and java) way of dependency handling is broken. Nonetheless what npm does for node.js namely nested, local dependencies just feels right. And although javascript land is a different world the same can be used at least for certain projects in clojure as well. Perhaps not all but some projects, specially the ones related to tooling and commons like libraries which a lot of other projects are depending on. These can benefit from this style of dependency handling.
**MrAnderson?! Why?**
At the end he really gets back to the source, does not he?
## License
Copyright © 2014 Benedek Fazekas
Distributed under the Eclipse Public License either version 1.0 or (at
your option) any later version.
================================================
FILE: build.sh
================================================
#!/bin/bash
# runs source-deps and tests and then provided lein target with mranderson
function check_result {
if [ $? -ne 0 ]; then
echo "FAILED"
exit 1
fi
}
lein do clean, inline-deps :skip-javaclass-repackage true
check_result
lein with-profile plugin.mranderson/config "$@"
================================================
FILE: gargamel.edn
================================================
{:sections
[{:key :fixes :regex ".*#\\d+.*" :title "New features, bugfixes"}
{:key :wip :regex ".*\\[[Ww][iI][pP]\\].*" :title "Work in progress, not yet ready"}]
:formattable-objects
[{:template "[#$1](https://github.com/%1$s/%2$s/issues/$1)" :regex "#(\\d+)"}
{:template "[$2: $3](https://github.com/$2/issues/$3)" :regex "(([_\\w/-]+)#(\\d+))"}
{:template "`$1`" :regex "(\\[refactor\\])"}
{:template "$1 " :regex "(\\n)"}]
;; directory for custom templates. should contain a template named changelog.mustache
;; which might include other templates mustache style with {{> othertemplate}}
:template-dir "md-templates"
;; file extension for the output
:output-extension ".md"}
================================================
FILE: java-src/mranderson/util/JjMainProcessor.java
================================================
/**
* Copied from Jar Jar Links 1.4
*
* Original licence
*
* Copyright 2007 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package mranderson.util;
import org.pantsbuild.jarjar.PatternElement;
import org.pantsbuild.jarjar.Rule;
import org.pantsbuild.jarjar.util.*;
import java.io.File;
import java.io.IOException;
import java.util.*;
public class JjMainProcessor implements JarProcessor
{
private final boolean verbose;
private final JarProcessorChain chain;
private final Map<String, String> renames = new HashMap<String, String>();
public JjMainProcessor(List<PatternElement> patterns, boolean verbose, boolean skipManifest) {
this.verbose = verbose;
List<Rule> ruleList = new ArrayList<Rule>();
for (PatternElement pattern : patterns) {
if (pattern instanceof Rule) {
ruleList.add((Rule) pattern);
}
}
JjPackageRemapper pr = new JjPackageRemapper(ruleList, verbose);
List<JarProcessor> processors = new ArrayList<JarProcessor>();
processors.add(new JarTransformerChain(new RemappingClassTransformer[]{ new RemappingClassTransformer(pr) }));
chain = new JarProcessorChain(processors.toArray(new JarProcessor[processors.size()]));
}
public void strip(File file) throws IOException {
return;
}
/**
* Returns the <code>.class</code> files to delete. As well the root-parameter as the rename ones
* are taken in consideration, so that the concerned files are not listed in the result.
*
* @return the paths of the files in the jar-archive, including the <code>.class</code> suffix
*/
private Set<String> getExcludes() {
return new HashSet<String>();
}
/**
*
* @param struct
* @return <code>true</code> if the entry is to include in the output jar
* @throws IOException
*/
public boolean process(EntryStruct struct) throws IOException {
String name = struct.name;
boolean keepIt = chain.process(struct);
if (keepIt) {
if (!name.equals(struct.name)) {
if (verbose)
System.err.println("Renamed " + name + " -> " + struct.name);
}
} else {
if (verbose)
System.err.println("Removed " + name);
}
return keepIt;
}
}
================================================
FILE: java-src/mranderson/util/JjPackageRemapper.java
================================================
/**
* Copied from Jar Jar Links 1.4
*
* Original licence
*
* Copyright 2007 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package mranderson.util;
import org.objectweb.asm.commons.Remapper;
import org.pantsbuild.jarjar.PatternElement;
import org.pantsbuild.jarjar.Rule;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
public class JjPackageRemapper extends Remapper
{
private static final String RESOURCE_SUFFIX = "RESOURCE";
private static final Pattern ARRAY_FOR_NAME_PATTERN
= Pattern.compile("\\[L[\\p{javaJavaIdentifierPart}\\.]+?;");
private final List<JjWildcard> wildcards;
private final Map<String, String> typeCache = new HashMap<String, String>();
private final Map<String, String> pathCache = new HashMap<String, String>();
private final Map<Object, String> valueCache = new HashMap<Object, String>();
private final boolean verbose;
private static List<JjWildcard> createWildcards(List<? extends PatternElement> patterns) {
List<JjWildcard> wildcards = new ArrayList<JjWildcard>();
for (PatternElement pattern : patterns) {
String result = (pattern instanceof Rule) ? ((Rule)pattern).getResult() : "";
String expr = pattern.getPattern();
if (expr.indexOf('/') >= 0)
throw new IllegalArgumentException("Patterns cannot contain slashes");
wildcards.add(new JjWildcard(expr.replace('.', '/'), result));
}
return wildcards;
}
public JjPackageRemapper(List<Rule> ruleList, boolean verbose) {
this.verbose = verbose;
wildcards = createWildcards(ruleList);
}
// also used by KeepProcessor
static boolean isArrayForName(String value) {
return ARRAY_FOR_NAME_PATTERN.matcher(value).matches();
}
public String map(String key) {
String s = typeCache.get(key);
if (s == null) {
s = replaceHelper(key);
if (key.equals(s))
s = null;
typeCache.put(key, s);
}
return s;
}
public String mapPath(String path) {
String s = pathCache.get(path);
if (s == null) {
s = path;
int slash = s.lastIndexOf('/');
String end;
if (slash < 0) {
end = s;
s = RESOURCE_SUFFIX;
} else {
end = s.substring(slash + 1);
s = s.substring(0, slash + 1) + RESOURCE_SUFFIX;
}
boolean absolute = s.startsWith("/");
if (absolute) s = s.substring(1);
s = replaceHelper(s);
if (absolute) s = "/" + s;
if (s.indexOf(RESOURCE_SUFFIX) < 0)
return path;
s = s.substring(0, s.length() - RESOURCE_SUFFIX.length()) + end;
pathCache.put(path, s);
}
return s;
}
public Object mapValue(Object value) {
if (value instanceof String) {
String s = valueCache.get(value);
if (s == null) {
s = (String)value;
if (isArrayForName(s)) {
String desc1 = s.replace('.', '/');
String desc2 = mapDesc(desc1);
if (!desc2.equals(desc1))
return desc2.replace('/', '.');
} else {
s = mapPath(s);
if (s.equals(value)) {
boolean hasDot = s.indexOf('.') >= 0;
boolean hasSlash = s.indexOf('/') >= 0;
if (!(hasDot && hasSlash)) {
if (hasDot) {
s = replaceHelper(s.replace('.', '/')).replace('/', '.');
} else {
s = replaceHelper(s);
}
}
}
}
valueCache.put(value, s);
}
// TODO: add back class name to verbose message
if (verbose && !s.equals(value))
System.err.println("Changed \"" + value + "\" -> \"" + s + "\"");
return s;
} else {
return super.mapValue(value);
}
}
private String replaceHelper(String value) {
for (JjWildcard wildcard : wildcards) {
String test = wildcard.replace(value);
if (test != null)
return test;
}
return value;
}
}
================================================
FILE: java-src/mranderson/util/JjWildcard.java
================================================
/**
* Copied from Jar Jar Links 1.4
*
* Original licence
*
* Copyright 2007 Google Inc.
*
* Copyright 2007 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package mranderson.util;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.ArrayList;
import java.util.Arrays;
public class JjWildcard
{
private static Pattern dstar = Pattern.compile("\\*\\*");
private static Pattern star = Pattern.compile("\\*");
private static Pattern estar = Pattern.compile("\\+\\??\\)\\Z");
private final Pattern pattern;
private final int count;
private final ArrayList<Object> parts = new ArrayList<Object>(16); // kept for debugging
private final String[] strings;
private final int[] refs;
public JjWildcard(String pattern, String result) {
if (pattern.equals("**"))
throw new IllegalArgumentException("'**' is not a valid pattern");
if (!checkIdentifierChars(pattern, "/*"))
throw new IllegalArgumentException("Not a valid package pattern: " + pattern);
if (pattern.indexOf("***") >= 0)
throw new IllegalArgumentException("The sequence '***' is invalid in a package pattern");
String regex = pattern;
regex = replaceAllLiteral(dstar, regex, "(.+?)");
regex = replaceAllLiteral(star, regex, "([^/]+)");
regex = replaceAllLiteral(estar, regex, "*)");
this.pattern = Pattern.compile("\\A" + regex + "\\Z");
this.count = this.pattern.matcher("foo").groupCount();
// TODO: check for illegal characters
char[] chars = result.toCharArray();
int max = 0;
for (int i = 0, mark = 0, state = 0, len = chars.length; i < len + 1; i++) {
char ch = (i == len) ? '@' : chars[i];
if (state == 0) {
if (ch == '@') {
parts.add(new String(chars, mark, i - mark));
mark = i + 1;
state = 1;
}
} else {
switch (ch) {
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
break;
default:
if (i == mark)
throw new IllegalArgumentException("Backslash not followed by a digit");
int n = Integer.parseInt(new String(chars, mark, i - mark));
if (n > max)
max = n;
parts.add(new Integer(n));
mark = i--;
state = 0;
}
}
}
int size = parts.size();
strings = new String[size];
refs = new int[size];
Arrays.fill(refs, -1);
for (int i = 0; i < size; i++) {
Object v = parts.get(i);
if (v instanceof String) {
strings[i] = ((String)v).replace('.', '/');
} else {
refs[i] = ((Integer)v).intValue();
}
}
if (count < max)
throw new IllegalArgumentException("Result includes impossible placeholder \"@" + max + "\": " + result);
// System.err.println(this);
}
public boolean matches(String value) {
return getMatcher(value) != null;
}
public String replace(String value) {
Matcher matcher = getMatcher(value);
if (matcher != null) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < strings.length; i++)
sb.append((refs[i] >= 0) ? matcher.group(refs[i]) : strings[i]);
return sb.toString();
}
return null;
}
private Matcher getMatcher(String value) {
Matcher matcher = pattern.matcher(value);
if (matcher.matches() && checkIdentifierChars(value, "/"))
return matcher;
return null;
}
private static boolean checkIdentifierChars(String expr, String extra) {
// package-info violates the spec for Java Identifiers.
// Nevertheless, expressions that end with this string are still legal.
// See 7.4.1.1 of the Java language spec for discussion.
if (expr.endsWith("package-info")) {
expr = expr.substring(0, expr.length() - "package-info".length());
}
for (int i = 0, len = expr.length(); i < len; i++) {
char c = expr.charAt(i);
if (extra.indexOf(c) >= 0)
continue;
if (!Character.isJavaIdentifierPart(c))
return false;
}
return true;
}
private static String replaceAllLiteral(Pattern pattern, String value, String replace) {
replace = replace.replaceAll("([$\\\\])", "\\\\$0");
return pattern.matcher(value).replaceAll(replace);
}
public String toString() {
return "Wildcard{pattern=" + pattern + ",parts=" + parts + "}";
}
}
================================================
FILE: md-templates/changelog.mustache
================================================
{{^to-build-num}}
# Changelog for [{{to}}]({{to-url}})
{{/to-build-num}}
{{#to-build-num}}
# Changelog for build [{{to-build-num}}]({{to-url}}) created at {{to-time}}
VCS tag: {{to}}
{{/to-build-num}}
{{^from-build-num}}
## Changes since [{{from}}]({{from-url}}) ([diff](compare-url))
{{/from-build-num}}
{{#from-build-num}}
## Changes since build [{{from-build-num}}]({{from-url}}) created at {{from-time}} ([diff]({{compare-url}}))
VCS tag: {{from}}
{{/from-build-num}}
{{#sections}}
### {{title}}
{{#commits}}
{{> commit}}
{{/commits}}
{{/sections}}
================================================
FILE: md-templates/commit.mustache
================================================
* [{{hash}}]({{url}}) (*{{project-name}}*) {{date}} [{{commiter}}] **{{linked-subject}}**
{{linked-body}}
================================================
FILE: project.clj
================================================
(def project-version "0.5.4-SNAPSHOT")
(defproject thomasa/mranderson project-version
:description "Dependency inlining and shadowing tool."
:url "https://github.com/benedekfazekas/mranderson"
:license {:name "Eclipse Public License"
:url "http://www.eclipse.org/legal/epl-v10.html"}
:eval-in :leiningen
:java-source-paths ["java-src"]
:javac-options ~(if (re-find #"^1\.8\." (System/getProperty "java.version"))
["-source" "8" "-source" "8"]
;; https://saker.build/blog/javac_source_target_parameters/index.html / https://archive.md/JH260
["--release" "8"])
:filespecs [{:type :bytes :path "mranderson/project.clj" :bytes ~(slurp "project.clj")}]
:dependencies [^:inline-dep [clj-commons/pomegranate "1.2.25"]
^:inline-dep [org.clojure/tools.namespace "1.5.1"]
^:inline-dep [clj-commons/fs "1.6.312"]
^:inline-dep [rewrite-clj "1.2.54"]
[org.clojure/clojure "1.10.3" :scope "provided"]
[org.pantsbuild/jarjar "1.7.2"]]
:mranderson {:project-prefix "mranderson.inlined"}
:profiles {:dev {:dependencies [[leiningen-core "2.12.0"]]}
:eastwood {:plugins [[jonase/eastwood "1.4.3"]]
:eastwood {:exclude-linters [:no-ns-form-found]}}
:mranderson-plugin {:plugins [[thomasa/mranderson ~project-version]]}
;; copy of plugin.mranderson/config profile, needed here so mrandersoned pom/jar can be built for mranderson itself
;; see Makefile for usage
:mranderson-profile ^:leaky {:omit-source true
:source-paths ["target/srcdeps"]
:filespecs [{:type :paths :paths ["target/srcdeps"]}]
:auto-clean false
:srcdeps-project-hacks true
:middleware [mranderson.plugin/middleware]
:jar-exclusions [#"(?i)^META-INF/.*"]}
:kaocha {:eval-in :sub-process
:dependencies [[lambdaisland/kaocha "1.91.1392"]
[lambdaisland/kaocha-cloverage "1.1.89"]]}}
:aliases {"kaocha" ["with-profile" "+kaocha" "run" "-m" "kaocha.runner"]
"kaocha-watch" ["with-profile" "+kaocha" "run" "-m" "kaocha.runner" "--watch"]
"kaocha-coverage" ["with-profile" "+kaocha" "run" "-m" "kaocha.runner" "--plugin" "cloverage"]})
================================================
FILE: scripts/integration_test.sh
================================================
#!/usr/bin/env bash
set -Eeuxo pipefail
cd "$(git rev-parse --show-toplevel)"
# An integration test that exercises that cider-nrepl and refactor-nrepl can successfully use mranderson @ latest.
# For that, we both build those projects with mranderson in them, and run their mrandersonized test suites.
make install
git submodule update --init --recursive
# cider-nrepl observes CI, which triggers :pedantic?, which is irrelevant here:
unset CI
cd test-resources/cider-nrepl
lein clean
lein with-profile -user,-dev inline-deps
lein with-profile -user,-dev,+1.10,+test,+plugin.mranderson/config test
cd ../refactor-nrepl
lein clean
make test
================================================
FILE: src/leiningen/inline_deps.clj
================================================
(ns leiningen.inline-deps
(:require [clojure.edn :as edn]
[me.raynes.fs :as fs]
[mranderson.core :as c]
[mranderson.util :as u]
[mranderson.log :as log])
(:import [java.util UUID]))
(defn- lookup-opt
([opt-key cli-opts project-opts]
(lookup-opt opt-key cli-opts project-opts nil))
([opt-key cli-opts project-opts not-found]
(if-let [cli-subseq (seq (drop-while #(not= % opt-key) cli-opts))]
(second cli-subseq)
(get project-opts opt-key not-found))))
(defn- generate-default-project-prefix []
(str "mranderson" (-> (UUID/randomUUID)
str
(.substring 0 8))))
(defn- lein-project->ctx
[{:keys [root target-path name version mranderson]} args]
(let [cli-opts (map edn/read-string args)
project-prefix (some-> (lookup-opt :project-prefix cli-opts mranderson
(generate-default-project-prefix))
clojure.core/name)
skip-repackage-java-classes (lookup-opt :skip-javaclass-repackage cli-opts mranderson)
prefix-exclusions (lookup-opt :prefix-exclusions cli-opts mranderson)
srcdeps-relative (str (apply str (drop (inc (count root)) target-path)) "/srcdeps")
project-source-dirs (filter fs/directory? (or (.listFiles (fs/file (str target-path "/srcdeps/"))) []))]
(log/debug "skip repackage" skip-repackage-java-classes)
(log/debug "project mranderson" (prn-str mranderson))
(log/info "project prefix: " project-prefix)
{:pname name
:pversion version
:pprefix project-prefix
:skip-repackage-java-classes skip-repackage-java-classes
:srcdeps srcdeps-relative
:prefix-exclusions prefix-exclusions
:project-source-dirs project-source-dirs
:unresolved-tree (lookup-opt :unresolved-tree cli-opts mranderson)
:overrides (lookup-opt :overrides cli-opts mranderson)
:expositions (lookup-opt :expositions cli-opts mranderson)
:watermark (lookup-opt :watermark cli-opts mranderson :mranderson/inlined)}))
(defn- initial-paths [target-path pprefix]
{:src-path (fs/file target-path "srcdeps" (u/sym->file-name pprefix))
:parent-clj-dirs []
:branch []})
(defn inline-deps
"Inline and shadow dependencies so they can not interfere with other libraries' dependencies.
Available options:
:project-prefix string Project prefix to use when shadowing
:skip-javaclass-repackage boolean If true Jar Jar Links won't be used to repackage java classes
:prefix-exclusions list List of prefixes that should not be processed in imports
:unresolved-tree boolean Enforces unresolved tree mode"
[{:keys [repositories dependencies target-path] :as project} & args]
(c/copy-source-files (u/determine-source-dirs project) target-path)
(let [{:keys [pprefix] :as ctx} (lein-project->ctx project args)
paths (initial-paths target-path pprefix)]
(c/mranderson repositories dependencies ctx paths)))
================================================
FILE: src/mranderson/core.clj
================================================
(ns mranderson.core
(:require [clojure.java.io :as io]
[clojure.string :as str]
[clojure.tools.namespace.file :refer [read-file-ns-decl]]
[me.raynes.fs :as fs]
[mranderson.dependency.resolver :as dr]
[mranderson.dependency.tree :as t]
[mranderson.move :as move]
[mranderson.log :as log]
[mranderson.util :as u])
(:import java.util.UUID
[java.util.zip ZipEntry ZipFile ZipOutputStream]))
;; inlined from leiningen source
(defn- print-dep [dep level]
(log/info (apply str (repeat (* 2 level) \space)) (pr-str dep)))
(defn- zip-target-file
[target-dir entry-path]
(let [entry-path (str/replace-first (str entry-path) #"^/" "")]
(fs/file target-dir entry-path)))
(defn- unzip
"Takes the path to a zipfile source and unzips it to target-dir."
([source]
(unzip source (name source)))
([source target-dir]
(with-open [zip (ZipFile. (fs/file source))]
(let [entries (enumeration-seq (.entries zip))
entry-pred (fn entry-pred [^java.util.zip.ZipEntry entry]
(not (or (.isDirectory entry)
(str/includes? (str entry) "META-INF")
(str/includes? (str entry) "clj-kondo.exports"))))
clj-files (transient [])]
(doseq [entry entries
:when (entry-pred entry)
:let [f (zip-target-file target-dir entry)
entry-name (.getName ^ZipEntry entry)]]
(fs/mkdirs (fs/parent f))
(with-open [in (.getInputStream zip entry)]
(io/copy in f))
(when (or (.endsWith ^String entry-name ".clj")
(.endsWith ^String entry-name ".cljc")
(.endsWith ^String entry-name ".cljs"))
(conj! clj-files entry-name)))
(persistent! clj-files)))))
(defn- cljfile->prefix [clj-file]
(->> (str/split clj-file #"/")
butlast
(str/join ".")))
(defn- possible-prefixes [clj-files]
(->> clj-files
(map cljfile->prefix)
(remove #(str/blank? %))
(remove #(= "clojure.core" %))
frequencies
keys
(map #(str/replace % "_" "-"))))
(defn- replacement-prefix [pprefix src-path art-name art-version underscorize?]
(let [path (->> (str/split (str src-path) #"/")
(drop-while #(not= (-> (u/sym->file-name pprefix)
(str/split #"/")
last)
%))
rest
(concat [pprefix])
(map #(if underscorize? % (str/replace % "_" "-"))))]
(->> [art-name art-version]
(concat path)
(str/join "."))))
(defn- replacement [prefix postfix underscorize?]
(->> (if underscorize?
(-> postfix
str
(str/replace "-" "_"))
postfix)
vector
(concat [prefix])
(str/join "." )
symbol))
(defn- update-path-in-file [file old-path new-path]
(let [old (slurp file)
new (str/replace old old-path new-path)]
(when-not (= old new)
(spit file new))))
(defn- import-fragment-left [^String clj-source]
(let [index-of-import (.indexOf clj-source ":import")]
(when (> index-of-import -1)
(drop (loop [ns-decl-fragment (reverse (take index-of-import clj-source))
index-of-open-bracket 1]
(cond (= \( (first ns-decl-fragment))
(- index-of-import index-of-open-bracket)
(not (re-matches #"\s" (-> ns-decl-fragment first str)))
(count clj-source)
:default (recur (rest ns-decl-fragment) (inc index-of-open-bracket)))) clj-source))))
(defn- import-fragment [clj-source]
(let [import-fragment-left (import-fragment-left clj-source)
frag-count (count import-fragment-left)]
(when (and import-fragment-left (> frag-count 0))
(loop [index 1
open-close 0]
(cond
(> open-close 0)
(apply str (take index import-fragment-left))
(>= index frag-count)
nil
:else
(recur (inc index) (cond (= \( (nth import-fragment-left index))
(dec open-close)
(= \) (nth import-fragment-left index))
(inc open-close)
:default open-close)))))))
(defn- retrieve-import [file-prefix file]
(let [cont (slurp (fs/file file-prefix file))
import-fragment (import-fragment cont)]
(when-not (str/blank? import-fragment)
[file import-fragment])))
(defn- find-orig-import [imports file]
(or (->> imports
(some (fn [[imp-file imp-fragment]]
(when (.endsWith (str file) (str imp-file))
imp-fragment))))
""))
(defn- class-deps-jar!
"creates jar containing the deps class files"
[]
(log/info "jaring all class file dependencies into target/class-deps.jar")
(with-open [file (io/output-stream "target/class-deps.jar")
zip (ZipOutputStream. file)
writer (io/writer zip)]
(let [class-files (u/class-files)]
(binding [*out* writer]
(doseq [class-file class-files]
(with-open [input (io/input-stream class-file)]
(.putNextEntry zip (ZipEntry. (u/remove-2parents class-file)))
(io/copy input zip)
(flush)
(.closeEntry zip)))))))
(defn- replace-class-deps! []
(log/info "deleting directories with class files in target/srcdeps...")
(doseq [class-dir (u/java-class-dirs)]
(fs/delete-dir (str "target/srcdeps/" class-dir))
(log/info " " class-dir " deleted"))
(log/info "unzipping repackaged class-deps.jar into target/srcdeps")
(unzip (fs/file "target/class-deps.jar") (fs/file "target/srcdeps/")))
(defn- filter-clj-files [imports package-names]
(if (seq package-names)
(->> (filter (fn [[_ import]] (some #(str/includes? import %) package-names)) imports)
(map first)
(map (partial str "target/srcdeps/")))
[]))
(defn- prefix-dependency-imports! [pname pversion pprefix prefix src-path srcdeps]
(let [cleaned-name-version (u/clean-name-version pname pversion)
prefix (some-> (first prefix)
(str/replace "-" "_")
(str/replace "." "/"))
clj-dep-path (u/relevant-clj-dep-path src-path prefix pprefix)
clj-files (u/clojure-source-files-relative clj-dep-path)
imports (->> clj-files
(reduce #(conj %1 (retrieve-import srcdeps (u/remove-2parents %2))) [])
(remove nil?)
doall)
class-names (map u/class-file->fully-qualified-name (u/class-files))
package-names (->> class-names
(map u/class-name->package-name)
set)
clj-files (filter-clj-files imports package-names)]
(when (seq clj-files)
(log/info (format " prefixing imports in clojure files in '%s' ..." (str/join ":" clj-dep-path)))
(log/debug " class-names" class-names)
(log/debug " package-names" package-names)
(log/debug " imports" imports)
(log/debug " clj files" (str/join ":" clj-files))
(doseq [file clj-files]
(let [old (slurp (fs/file file))
orig-import (find-orig-import imports file)
new-import (reduce #(str/replace %1 (re-pattern (str "([^\\.])" %2)) (str "$1" cleaned-name-version "." %2)) orig-import package-names)
uuid (str (UUID/randomUUID))
new (str/replace old orig-import uuid)
new (reduce #(str/replace %1 (re-pattern (str "([^\\.])" %2)) (str "$1" cleaned-name-version "." %2)) new class-names)
new (str/replace new uuid new-import)]
(when-not (= old new)
(log/debug "file: " file " orig import:" orig-import " new import:" new-import)
(spit file new))))
(log/info " prefixing imports: done"))))
(defn- update-artifact!
[{:keys [pname pversion pprefix skip-repackage-java-classes srcdeps prefix-exclusions project-source-dirs expositions watermark]}
{:keys [art-name-cleaned art-version clj-files clj-dirs dep]}
{:keys [src-path parent-clj-dirs branch]}]
(let [repl-prefix (replacement-prefix pprefix src-path art-name-cleaned art-version nil)
prefixes (apply dissoc (reduce #(assoc %1 %2 (str (replacement repl-prefix %2 nil))) {} (possible-prefixes clj-files)) prefix-exclusions)
expose? (first (filter (partial t/path-pred branch dep) expositions))
all-deps-dirs (->> (concat
[src-path]
(map fs/file clj-dirs)
parent-clj-dirs)
vec
(mapv str)
u/normalize-dirs
(mapv fs/file))]
(log/info (format " munge source files of %s artifact on branch %s exposed %s." art-name-cleaned branch (boolean expose?)))
(log/debug " proj-source-dirs" project-source-dirs " clj files" clj-files "clj dirs" clj-dirs " path to dep" src-path "parent-clj-dirs: " parent-clj-dirs)
(log/debug " modified namespace prefix: " repl-prefix)
(log/debug " src path: " src-path)
(log/debug " parent clj dirs: " (str/join ":" parent-clj-dirs))
(log/debug " all dirs: " all-deps-dirs)
(log/debug (format " modified dependency name: %s modified version string: %s" art-name-cleaned art-version))
(when-not skip-repackage-java-classes
(if (str/ends-with? (str src-path) (u/sym->file-name pprefix))
(doall
(map #(prefix-dependency-imports! pname pversion pprefix % (str src-path) srcdeps) prefixes))
(prefix-dependency-imports! pname pversion pprefix nil (str src-path) srcdeps)))
(doseq [clj-file clj-files
:when (.exists (fs/file srcdeps clj-file))];; some cljc file might have been moved with their platform specific file
(if-let [old-ns (some->> clj-file (fs/file srcdeps) read-file-ns-decl second)]
(let [new-ns (replacement repl-prefix old-ns nil)]
(log/debug " new ns:" new-ns)
(move/move-ns old-ns new-ns srcdeps (u/file->extension (str clj-file)) all-deps-dirs watermark)
(when (or (str/ends-with? src-path (u/sym->file-name pprefix)) expose?)
(move/replace-ns-symbol-in-source-files old-ns new-ns (u/file->extension (str clj-file)) project-source-dirs nil)))
;; a clj file without ns
(when-not (= "project.clj" clj-file)
(let [old-path (fs/file srcdeps clj-file)
new-path (str (u/sym->file-name pprefix) "/" art-name-cleaned "/" art-version "/" clj-file)]
(fs/copy+ old-path (fs/file srcdeps new-path))
;; replace occurrences of file path references
(doseq [file (u/clojure-source-files [srcdeps])]
(update-path-in-file file clj-file new-path))
;; remove old file
(fs/delete old-path)))))))
(defn- remove-invalid-duplicates!
"See issue #44. Some artifacts package duplicate files in weird locations
(e.g. riddley has `riddley.compiler` in the root of the package) which
causes a mismatch of expectations when moving files, resulting in
exceptions."
[srcdeps clj-files]
(let [by-ns (group-by
#(->> (fs/file srcdeps %)
(read-file-ns-decl)
(second))
clj-files)]
(mapcat
(fn [[nspace files]]
(if (and nspace (next files))
;; A namespace has been duplicated, so we need to find the files that
;; actually exist. There might be more than one file, in case of
;; CLJ/CLJS duplication.
(let [expected-prefix (u/sym->file-name nspace)
match? #{(str expected-prefix ".clj")
(str expected-prefix ".cljs")
(str expected-prefix ".cljc")}]
(doseq [to-delete (remove match? files)]
(log/warn " removing duplicated file with namespace mismatch:" to-delete)
(.delete (fs/file srcdeps to-delete)))
(filter match? files))
;; Only one file means nothing to do (but it might still be in the
;; wrong place).
files))
by-ns)))
(defn- order-by-extension
"Make sure that first clj and cljs files considered, cljc files only after them."
[clj-files]
(sort-by #(str/replace % ".cljc" ".cljx") clj-files))
(defn- unzip-artifact! [{:keys [srcdeps]} {:keys [src-path branch]} dep]
(let [art-name (-> dep first name (str/split #"/") last)
art-name-cleaned (str/replace art-name #"[\.-_]" "")
art-version (str "v" (-> dep second (str/replace "." "v")))
_ (log/info "unzipping [" art-name-cleaned " [" art-version "]]")
clj-files (->> (unzip (-> dep meta :file) srcdeps)
(remove-invalid-duplicates! srcdeps)
order-by-extension
(doall))
clj-dirs (u/clj-files->dirs srcdeps clj-files)]
(log/debug (format "resolving transitive dependencies for %s:" art-name))
[{:art-name-cleaned art-name-cleaned
:art-version art-version
:clj-files clj-files
:clj-dirs clj-dirs
:dep dep}
{:src-path (fs/file src-path (str/replace (str/join "/" [art-name-cleaned art-version]) "-" "_"))
:branch (conj branch (first dep))
:parent-clj-dirs (map fs/file clj-dirs)}]))
(defn copy-source-files
([source-paths target-path]
(copy-source-files source-paths target-path "srcdeps"))
([source-paths target-path target-suffix]
(let [to (-> target-path (io/file target-suffix) .toString)]
(doseq [source-path source-paths]
(fs/copy-dir-into source-path to)))))
(defn- mranderson-unresolved-deps!
"Unzips and transforms files in an unresolved dependency tree."
[unresolved-deps-tree paths ctx]
(let [unresolved-deps-tree (t/evict-subtrees unresolved-deps-tree '#{org.clojure/clojure org.clojure/clojurescript org.clojure/core.rrb-vector})]
(log/info "in UNRESOLVED-TREE mode, working on an unresolved dependency tree")
(t/walk-deps unresolved-deps-tree print-dep)
(t/walk-dep-tree unresolved-deps-tree unzip-artifact! update-artifact! paths ctx)))
(defn- mranderson-resolved-deps!
"Unzips and transforms files in a resolved dependency tree.
Creates a topological order based on the expanded tree. Flattens out the resolved tree into a list like data structure
ordered by the expanded tree based topological order. Processes this ordered list with `walk-ordered-deps` that first
unzips all deps and collects their contextual info and then performs the source transformation in
reverse topological order."
[resolved-deps unresolved-deps paths ctx]
(let [unresolved-deps-topo-order (t/topological-order unresolved-deps)
topo-comparator (fn [[l] [r]]
(compare (get unresolved-deps-topo-order l Long/MAX_VALUE)
(get unresolved-deps-topo-order r Long/MAX_VALUE)))
resolved-deps (t/evict-subtrees resolved-deps '#{org.clojure/clojure org.clojure/clojurescript})
ordered-resolved-deps (->> (tree-seq map? (fn [m] (concat (keys m) (vals m))) resolved-deps)
(filter vector?)
(reduce (fn [m dep] (assoc m dep nil)) {})
(into (sorted-map-by topo-comparator)))]
(log/info "in RESOLVED-TREE mode, working on a resolved dependency tree")
(t/walk-deps resolved-deps print-dep)
(t/walk-ordered-deps
ordered-resolved-deps
unzip-artifact!
update-artifact!
paths
ctx)))
(defn mranderson
"Inline and shadow dependencies so they can not interfere with other libraries' dependencies.
`repositories` to resolve dependencies, `dependencies` list of dependencies to inline and shadow, `ctx` for opts and project specific attributes, `paths` for project specific paths.
`ctx` in detail:
- pname: project name
- pversion: project version
- pprefix: project prefix, defaults to mranderson{rnd}
- skip-repackage-java-classes: Skips shadowing java classes part of a dependency if true
- prefix-exclusions: prefixes to exclude when prefixing imports for java classes
- unresolved-tree: switch to unresolved tree mode if true
- overrides: overrides in the unresolved tree in unresolved tree mode
- expositions: transient dependencies made available for the project source files in unresolved tree mode
- watermark: meta flag to mark inlined dependencies"
[repositories dependencies {:keys [skip-repackage-java-classes unresolved-tree pname pversion overrides] :as ctx} paths]
(let [source-dependencies (filter u/source-dep? dependencies)
resolved-deps-tree (dr/resolve-source-deps repositories source-dependencies)
overrides (or (and unresolved-tree overrides) {})
unresolved-deps-tree (dr/expand-dep-hierarchy repositories resolved-deps-tree overrides)]
(log/info "retrieve dependencies and munge clojure source files")
(if unresolved-tree
(mranderson-unresolved-deps! unresolved-deps-tree paths ctx)
(mranderson-resolved-deps! resolved-deps-tree unresolved-deps-tree paths ctx))
(when-not (or skip-repackage-java-classes (empty? (u/class-files)))
(class-deps-jar!)
(u/apply-jarjar! pname pversion)
(replace-class-deps!))))
================================================
FILE: src/mranderson/dependency/resolver.clj
================================================
(ns mranderson.dependency.resolver
(:require [cemerick.pomegranate.aether :as aether]
[mranderson.dependency.tree :as t]))
(defn resolve-source-deps
"Retrieves the given dependencies using the given `repositories`"
[repositories source-dependencies]
(->> (aether/resolve-dependencies :coordinates source-dependencies :repositories repositories)
(aether/dependency-hierarchy source-dependencies)))
(defn expand-dep-hierarchy
"Expands the first level of the given dep hierarchy into an unresolved dependency tree.
Understands overrides so a node can be overriden at any point of the tree while it is being built."
[repositories dep-hierarchy overrides]
(t/walk&expand-deps
(zipmap (keys dep-hierarchy) (repeat nil))
(partial resolve-source-deps repositories)
overrides))
================================================
FILE: src/mranderson/dependency/tree.clj
================================================
(ns mranderson.dependency.tree
(:require [clojure.tools.namespace.dependency :as dep]))
;; inlined from leiningen source
(defn walk-deps
([deps f level]
(doseq [[dep subdeps] deps]
(f dep level)
(when subdeps
(walk-deps subdeps f (inc level)))))
([deps f]
(walk-deps deps f 0)))
(defn path-pred [path dep k]
(= k (conj path (first dep))))
(defn walk&expand-deps
"Walks a dependency tree and expands it with all dependencies as walking.
This essentially means that an unresolved dependency tree is created where all nodes hold their originally
defined dependencies as children recursively.
Understands overrides in terms of versions so a node can be overridden at any point of the tree while it is being built.
This means that if the old version of the same dependency has different dependencies in its turn than the new version
being enforced by overriding the dependencies of the new version will be present in the subtree.
Overrides is a map where keys are paths as vectors to a dependency in an unresolved tree and values are dependencies
as a vector. for example
```
{[mvxcvi/puget fipp] [fipp 0.6.14]}
```
"
([deps resolve-dep-fn overrides path]
(->> (map
(fn [[dep _]]
(let [[_override-k override-v] (first (filter (comp (partial path-pred path dep) key) overrides))
[dep subdeps] (first (resolve-dep-fn [(or override-v dep)]))]
[dep
(when (seq subdeps)
(walk&expand-deps subdeps resolve-dep-fn overrides (conj path (first dep))))]))
deps)
(into {})))
([deps resolve-dep-fn overrides]
(walk&expand-deps deps resolve-dep-fn overrides [])))
(defn evict-subtrees
"Evict subtrees from a dependency tree.
`subtree-roots` are defined as a set of dependency names (for example `#{'org.clojure/clojure 'org.clojure/clojurescript}`
without their versions. Tested on a resolved tree. Assumed that it would evict all subtrees from an unresolved depedency
tree."
[deps subtree-roots]
(->> (map
(fn [[dep subdeps]]
(let [subdeps (remove (comp subtree-roots ffirst) subdeps)]
[dep (when (seq subdeps) (evict-subtrees subdeps subtree-roots))]))
deps)
(into {})))
(defn walk-dep-tree
"Walks a dependency tree in depth first order.
Applies `pre-fn` on node before going down a level. `pre-fn` calculates and returns its own context and paths,
these are passed down to the next level. After the subtree is processed `post-fn` gets applied using the context,
paths returned by `pre-fn` for the same node."
[deps pre-fn post-fn paths ctx]
(doseq [[dep subdeps] deps]
(let [[pre-result new-paths] (pre-fn ctx paths dep)]
(when subdeps
(walk-dep-tree subdeps pre-fn post-fn new-paths ctx))
(post-fn ctx pre-result paths))))
(defn walk-ordered-deps
"Walks a flat list of dependencies.
Applies `pre-fn` on all the dependencies and collects the `pre-fn` returned contextual values and paths.
Runs `post-fn` on all the dependencies in a reverse order using the `pre-fn` results and paths."
[deps pre-fn post-fn paths ctx]
(->> (keys deps)
(map (partial pre-fn ctx paths))
((juxt (partial reduce (fn [clj-dirs [_ {:keys [parent-clj-dirs]}]] (into clj-dirs parent-clj-dirs)) []) reverse))
((fn [[clj-dirs deps]]
(dorun (map (fn [[pre-result _]] (post-fn ctx pre-result (update paths :parent-clj-dirs concat clj-dirs))) deps))))))
(defn- create-dep-graph
([graph deps level]
(reduce
(fn [graph [dep subdeps]]
(if subdeps
(create-dep-graph (reduce (fn [g sd] (dep/depend g (ffirst sd) (first dep))) graph subdeps) subdeps (inc level))
(if (= 0 level)
(dep/depend graph nil (first dep))
graph)))
graph
deps))
([deps]
(create-dep-graph (dep/graph) deps 0)))
(defn topological-order [dep-tree]
(zipmap (dep/topo-sort (create-dep-graph dep-tree)) (range)))
================================================
FILE: src/mranderson/log.clj
================================================
(ns ^:no-doc mranderson.log
"Here we replicate lein logging.
This gives us what a lein plugin user would expect and is also reasonable for non-lein use.
We don't use's lein's logging methods because we don't want to tie ourselves to lein just for logging.
We also add in multi-threading support which lein logging does not do.")
;; lein-isms for controlling logging
(def ^:private log-info+? (not (System/getenv "LEIN_SILENT")))
(def ^:private log-debug? (System/getenv "DEBUG"))
;; a lock so (at least our) log line output do not get inter-mingled if we decide to log
;; from multi-threaded work
(def ^:private lock (Object.))
(defn info
"Lein sends info to stdout, is silenced by LEIN_SILENT, so we do that too."
[& args]
(when log-info+?
(locking lock
(apply println args))))
(defn warn
"Lein warns to stderr, is silenced by LEIN_SILENT, so we match that."
[& args]
(when log-info+?
(locking lock
(binding [*out* *err*]
(apply println args)))))
(defn debug
"Lein's debug is enabled by DEBUG, sent to stdout, and is unaffected by LEIN_SILENT,
so we replicate that behaviour"
[& args]
(when log-debug?
(locking lock
(apply println args))))
================================================
FILE: src/mranderson/move.clj
================================================
;; Copyright (c) Stuart Sierra, 2012. All rights reserved. The use and
;; distribution terms for this software are covered by the Eclipse
;; Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
;; which can be found in the file epl-v10.html at the root of this
;; distribution. By using this software in any fashion, you are
;; agreeing to be bound by the terms of this license. You must not
;; remove this notice, or any other, from this software.
(ns ^{:author "Stuart Sierra, Benedek Fazekas"
:doc "Refactoring tool to move a Clojure namespace from one name/file to
another, and update all references to that namespace in your other
Clojure source files.
WARNING: This code is ALPHA and subject to change. It also modifies
and deletes your source files! Make sure you have a backup or
version control.
DISCLAIMER
This is a heavily modified version of Stuart Sierra's original clojure.tools.namespace.move
"}
mranderson.move
(:require [clojure.string :as str]
[clojure.java.io :as io]
[mranderson.util :as util]
[me.raynes.fs :as fs]
[rewrite-clj.zip :as z]
[rewrite-clj.node :as n]
[rewrite-clj.parser :as parser]
[rewrite-clj.reader :as reader])
(:import (java.io File FileNotFoundException)))
(defn- update-file
"Reads file as a string, calls f on the string plus any args, then
writes out return value of f as the new contents of file. Does not
modify file if the content is unchanged."
[file f & args]
(let [old (slurp file)
new (str (apply f old args))]
(when-not (= old new)
(spit file new))))
(defn- sym->file ^File
[path sym extension]
(io/file path (str (util/sym->file-name sym) extension)))
(defn- update? [file extension-of-moved]
(let [file-ext (util/file->extension file)
all-extensions #{".cljc" ".cljs" ".clj"}]
(or
(and (= ".cljc" extension-of-moved)
(all-extensions file-ext))
(= file-ext extension-of-moved)
(= file-ext ".cljc"))))
(defn- clojure-source-files [dirs extension]
(->> dirs
(map io/file)
(filter #(.exists ^File %))
(mapcat file-seq)
(filter (fn [^File file]
(and (.isFile file)
(update? (str file) extension))))
(mapv fs/normalized)))
(defn- prefix-libspec [libspec]
(let [prefix (str/join "." (butlast (str/split (name libspec) #"\.")))]
(and prefix (symbol prefix))))
(defn- java-package [sym]
(str/replace (name sym) "-" "_"))
(defn- java-style-prefix?
[old-sym node]
(when-not (#{:uneval} (z/tag node))
(when-let [node-sexpr (z/sexpr node)]
(str/starts-with? node-sexpr (java-package old-sym)))))
(defn- libspec-prefix?
[node node-sexpr old-sym]
(let [old-sym-prefix-libspec (prefix-libspec old-sym)
first-node? (z/leftmost? node)
parent-leftmost-node (z/leftmost (z/up node))
parent-leftmost-sexpr (and parent-leftmost-node
(not
(#{:uneval}
(z/tag parent-leftmost-node)))
(z/sexpr parent-leftmost-node))]
(and first-node?
(= :require parent-leftmost-sexpr)
(= node-sexpr old-sym-prefix-libspec))))
(defn- contains-sym? [old-sym node]
(when-not (#{:uneval} (z/tag node))
(when-let [node-sexpr (z/sexpr node)]
(or
(= node-sexpr old-sym)
(libspec-prefix? node node-sexpr old-sym)))))
(defn- ->new-node [old-node old-sym new-sym]
(let [old-prefix (prefix-libspec old-sym)]
(cond-> old-node
:always
(str/replace-first
(name old-sym)
(name new-sym))
(= old-prefix old-node)
(str/replace-first
(name old-prefix)
(name (prefix-libspec new-sym))))))
(defn- replace-in-node [old-sym new-sym old-node]
(let [new-node (->new-node old-node old-sym new-sym)]
(cond
(symbol? old-node) (symbol new-node)
:default new-node)))
(defn- ns-decl? [node]
(when-not (#{:uneval} (z/tag node))
(= 'ns (z/sexpr (z/down node)))))
(def ^:const ns-form-placeholder (str "ns_" "form_" "placeholder"))
(defn- split-ns-form-ns-body
"Returns ns form as a rewrite-clj loc and the ns body as string with a place holder for the ns form."
[content]
(let [reader (reader/string-reader content)
first-form (parser/parse reader)]
(loop [ns-form-maybe (and first-form (z/of-node first-form))]
(if (ns-decl? ns-form-maybe)
[ns-form-maybe
(str/replace content (z/root-string ns-form-maybe) ns-form-placeholder)]
(if-let [next-form (parser/parse reader)]
(recur (z/of-node next-form))
[nil content])))))
(defn- import? [node]
(when-not (#{:uneval} (z/tag node))
(when-let [node-sexpr (z/sexpr node)]
(= :import node-sexpr))))
(defn- ->new-import-node [old-sym new-sym old-node]
(let [new-node (str/replace old-node (java-package old-sym) (java-package new-sym))]
(cond
(symbol? old-node) (symbol new-node)
:default new-node)))
(defn- replace-in-import* [import-loc old-sym new-sym]
(loop [loc import-loc]
(if-let [found-node (some-> loc
(z/find-next-depth-first (partial java-style-prefix? old-sym))
(z/edit (partial ->new-import-node old-sym new-sym)))]
(recur found-node)
(z/root loc))))
(defn- replace-in-import [ns-loc old-sym new-sym]
(if-let [import-loc (some-> (z/find-next-depth-first ns-loc import?)
(z/up))]
(-> (z/replace import-loc (replace-in-import* (z/of-node (z/node import-loc)) old-sym new-sym))
z/root
z/of-node)
ns-loc))
(defn- zskip-unintresting
;; TODO: move to a zloc ns and expand to handle all zip operations
"Rewrite-clj only skips whitespace and comments.
We'd also like to skip reader discard #_ nodes (aka uneval nodes in rewrite-clj)."
[zloc]
(z/skip z/right* #(or (z/whitespace-or-comment? %)
(= :uneval (z/tag %)))
zloc))
(defn- zdown [zloc]
(some-> zloc z/down* zskip-unintresting))
(defn- zright [zloc]
(some-> zloc z/right* zskip-unintresting))
(defn ^:no-doc rename-ns
;; exposed only for unit testing, could move to impl ns
"Return `ns-loc`, with zipper location unchanged, applying `new-ns-name` and `add-meta-kw`,
iff current namespace name is `old-ns-name`, else return `ns-loc`.
`ns-loc` is assumed to be positioned at the `(ns ...)` form (or nil).
We don't look at or alter `ns` form's `attr-map?`.
We make an effort to preserve existing ordering and syntax of metadata."
[ns-loc old-ns-name new-ns-name add-meta-kw]
(when ns-loc
(let [ns-name-loc (some-> ns-loc zdown zright)
cur-has-meta? (= :meta (z/tag ns-name-loc))
ns-loc (cond
(not= (z/sexpr ns-name-loc) old-ns-name)
ns-loc
add-meta-kw
(if cur-has-meta?
(cond-> (zdown ns-name-loc)
;; convert existing ^:some-meta to ^{:some-meta true ...}
(-> ns-name-loc zdown z/node n/keyword-node?)
(z/edit (fn [kw] (n/map-node [kw (n/spaces 1) true])))
;; append our new meta to existing ^{:some-meta true ...}
:always
(-> (z/assoc add-meta-kw true)
zright
(z/replace new-ns-name)))
;; no existing meta, add it in
(-> ns-name-loc
(z/replace (n/meta-node
(n/map-node [add-meta-kw (n/spaces 1) true])
new-ns-name))))
cur-has-meta?
(-> ns-name-loc
zdown ;; to current meta
zright ;; to namespace name
(z/replace new-ns-name))
:else
(z/replace ns-name-loc new-ns-name))]
;; we could maybe not rebuild zipper? but for now, go with the flow
(-> ns-loc
z/root
z/of-node))))
(defn- replace-in-ns-form [ns-loc old-sym new-sym watermark]
(loop [loc (-> (rename-ns ns-loc old-sym new-sym watermark)
(replace-in-import old-sym new-sym))]
(if-let [found-node (some-> (z/find-next-depth-first loc (partial contains-sym? old-sym))
(z/edit (partial replace-in-node old-sym new-sym)))]
(recur found-node)
(z/root-string loc))))
(defn- source-replacement [old-sym new-sym match]
(let [old-ns-ref (name old-sym)
new-ns-ref (name new-sym)
old-ns-ref-dot (str old-ns-ref ".")
new-ns-ref-dot (str new-ns-ref ".")
old-pkg-prefix (java-package old-sym)
new-pkg-prefix (java-package new-sym)
old-type-prefix (str "^" (java-package old-sym))
new-type-prefix (str "^" (java-package new-sym))]
(cond
(= match old-ns-ref)
new-ns-ref
(and (str/starts-with? match old-pkg-prefix)
(str/includes? match "_"))
(str/replace match old-pkg-prefix new-pkg-prefix)
(str/starts-with? match old-type-prefix)
(str/replace match old-type-prefix new-type-prefix)
(str/starts-with? match old-ns-ref-dot)
(str/replace match old-ns-ref-dot new-ns-ref-dot)
:default
match)))
(def ^:private symbol-regex
;; LispReader.java uses #"[:]?([\D&&[^/]].*/)?([\D&&[^/]][^/]*)" but
;; that's too broad; we don't want a whole namespace-qualified symbol,
;; just each part individually.
#"\"?[a-zA-Z0-9$%*+=?!<>^_-]['.a-zA-Z0-9$%*+=?!<>_-]*")
(defn- replace-in-source [source-sans-ns old-sym new-sym]
(str/replace source-sans-ns symbol-regex (partial source-replacement old-sym new-sym)))
(defn- after-platform-marker? [platform node]
(when-not (#{:uneval} (z/tag node))
(= platform (z/sexpr (z/left node)))))
(defn- find-and-replace-platform-specific-subforms [platform ns-loc]
(loop [loc ns-loc
found-nodes []]
(if-let [found-node (z/find-next-depth-first loc (partial after-platform-marker? platform))]
(recur (z/replace found-node (symbol (str (name platform) "_require"))) (conj found-nodes found-node))
[found-nodes (z/of-node (z/root loc))])))
(defn- restore-platform-specific-subforms [platform replaced-nodes ns-form]
(loop [form ns-form
[n & rest-nodes] replaced-nodes]
(if-not n
form
(recur (str/replace-first form (str (name platform) "_require") (z/string n)) rest-nodes))))
(defn replace-ns-symbol
"ALPHA: subject to change. Given Clojure source as a file, replaces
all occurrences of the namespace name old-sym with new-sym and
returns modified source as a string.
Splits the source file, parses the ns macro if found to do all the necessary
transformations. Works on the body of namespace as text as simpler transformations
are needed. When done puts the ns form and body back together."
[content old-sym new-sym watermark extension-of-moved file-ext]
(let [[ns-loc source-sans-ns] (split-ns-form-ns-body content)
opposite-platform (util/platform-comp (util/extension->platform extension-of-moved))
[replaced-nodes ns-loc] (or (and ns-loc
(= ".cljc" file-ext)
opposite-platform
(find-and-replace-platform-specific-subforms opposite-platform ns-loc))
[[] ns-loc])
new-ns-form (replace-in-ns-form ns-loc old-sym new-sym watermark)
new-source-sans-ns (replace-in-source source-sans-ns old-sym new-sym)
new-ns-form (if (seq replaced-nodes)
(restore-platform-specific-subforms opposite-platform replaced-nodes new-ns-form)
new-ns-form)]
(or
(and
new-ns-form
(str/replace new-source-sans-ns ns-form-placeholder new-ns-form))
new-source-sans-ns)))
(defn move-ns-file
"ALPHA: subject to change. Moves the .clj or .cljc source file (found relative
to source-path) for the namespace named old-sym to a file for a
namespace named new-sym.
WARNING: This function moves and deletes your source files! Make
sure you have a backup or version control."
[old-sym new-sym extension source-path]
(if-let [old-file (sym->file source-path old-sym extension)]
(let [new-file (sym->file source-path new-sym extension)]
(.mkdirs (.getParentFile new-file))
(io/copy old-file new-file)
(.delete old-file)
(loop [dir (.getParentFile old-file)]
(when-let [files (.listFiles dir)]
(when (empty? files)
(.delete dir)
(recur (.getParentFile dir))))))
(throw (FileNotFoundException. (format "file for %s not found in %s" old-sym source-path)))))
(def ^:private pmap-runner
"Removing parallelism might alleviate https://github.com/benedekfazekas/mranderson/issues/56"
(if (System/getProperty "mranderson.internal.no-parallelism")
map
pmap))
(defn replace-ns-symbol-in-source-files
"Replaces all occurrences of the old name with the new name in
all Clojure source files found in dirs."
[old-sym new-sym extension dirs watermark]
(let [files (clojure-source-files dirs extension)]
(util/assert-no-duplicate-files files)
(->> files
(pmap-runner (fn [file]
(->> (str file)
util/file->extension
(update-file file replace-ns-symbol old-sym new-sym watermark extension))))
(doall))))
(defn move-ns
"ALPHA: subject to change. Moves the .clj or .cljc source file (found relative
to source-path) for the namespace named old-sym to new-sym and
replace all occurrences of the old name with the new name in all
Clojure source files found in dirs.
This is partly textual transformation. It works on
namespaces require'd or use'd from a prefix list.
WARNING: This function modifies and deletes your source files! Make
sure you have a backup or version control."
[old-sym new-sym source-path extension dirs watermark]
(move-ns-file old-sym new-sym extension source-path)
;; move cljc file with the platform specific file if exists
(when (and (#{".clj" ".cljs"} extension) (.exists (sym->file source-path old-sym ".cljc")))
(move-ns-file old-sym new-sym ".cljc" source-path))
(replace-ns-symbol-in-source-files old-sym new-sym extension dirs watermark))
================================================
FILE: src/mranderson/plugin.clj
================================================
(ns mranderson.plugin
(:require [mranderson.util :refer [first-src-path clojure-source-files source-dep?]]
[clojure.tools.namespace.file :refer [read-file-ns-decl]]))
(defn- find-gen-class-ns [found file]
(let [ns-decl (read-file-ns-decl file)]
(if (and ns-decl (.contains ^String (apply str ns-decl) ":gen-class"))
(conj found (second ns-decl))
found)))
(defn middleware
"Handles :gen-class instances in deps which need AOT"
[{:keys [source-paths root aot dependencies srcdeps-project-hacks] :as project}]
(if srcdeps-project-hacks
(let [src (first-src-path root source-paths)
new-aot (reduce find-gen-class-ns aot (clojure-source-files [src]))]
(-> project
(assoc :dependencies (remove source-dep? dependencies))
(assoc :aot new-aot)))
project))
================================================
FILE: src/mranderson/profiles.clj
================================================
{:config ^:leaky {:omit-source true
:source-paths ["target/srcdeps"]
:filespecs [{:type :paths :paths ["target/srcdeps"]}]
:auto-clean false
:srcdeps-project-hacks true
:jar-exclusions [#"(?i)^META-INF/.*"]}}
================================================
FILE: src/mranderson/util.clj
================================================
(ns mranderson.util
(:require [clojure.edn :as edn]
[clojure.java.io :as io]
[clojure.string :as str]
[me.raynes.fs :as fs]
[clojure.set :as s]
[mranderson.log :as log])
(:import [java.io File]
[org.pantsbuild.jarjar Rule]
[mranderson.util JjMainProcessor]
[org.pantsbuild.jarjar.util StandaloneJarProcessor]))
(defn clojure-source-files-relative
([dirs excl-dir]
(let [excl-dirs (when excl-dir (map #(str % "/" excl-dir) dirs))]
(->> dirs
(map io/file)
(filter #(.exists ^File %))
(mapcat file-seq)
(remove (fn [file]
(some #(.startsWith (str file) %) excl-dirs) ))
(filterv (fn [^File file]
(let [file-name (.getName file)]
(and (.isFile file)
(or
(.endsWith file-name ".cljc")
(.endsWith file-name ".cljs")
(.endsWith file-name ".clj")))))))))
([dirs]
(clojure-source-files-relative dirs nil)))
(defn sym->file-name
[sym]
(-> (name sym)
(str/replace "-" "_")
(str/replace "." File/separator)))
(defn relevant-clj-dep-path [src-path prefix pprefix]
(let [pprefix-path-frag (sym->file-name pprefix)]
(if-not (str/ends-with? src-path pprefix-path-frag)
(vector (str "target/srcdeps/"
pprefix-path-frag
(-> src-path
(str/split (re-pattern pprefix-path-frag))
last)))
[(str "target/srcdeps/" prefix)])))
(defn clojure-source-files [dirs]
(->> dirs
clojure-source-files-relative
(map #(.getCanonicalFile ^File %))))
(defn class-files []
(let [dir (io/file "target/srcdeps")
subdirs (some-> (.listFiles ^File dir) seq)]
(->> subdirs
(filter #(.isDirectory ^File %))
(mapcat file-seq)
(filterv (fn [^File file]
(and (.isFile file)
(.endsWith (.getName file) ".class")))))))
(defn class-file->fully-qualified-name [file]
(->> (-> file
str
(str/split #"\.")
first
(str/split #"/"))
(drop 2)
(str/join ".")))
(defn class-name->package-name [class-name]
(->> (str/split class-name #"\.")
butlast
(str/join ".")))
(defn java-class-dirs
"lists subdirs of target/srcdeps which contain .class files"
[]
(reduce #(->> (str/split (str %2) #"/")
(drop 2)
first
((partial conj %1))) #{} (class-files)))
(defn clean-name-version
[pname pversion]
(str (str/replace pname #"[\._-]" "")
(str/replace pversion #"[\._-]" "")))
(defn first-src-path [root source-paths]
(apply str (drop (inc (count root)) (first source-paths))))
(defn source-dep? [dependency]
(let [{:keys [source-dep inline-dep]} (meta dependency)]
(or source-dep inline-dep)))
(defn- create-rule [name-version java-dir]
(let [rule (Rule.)]
(. rule setPattern (str java-dir ".**"))
(. rule setResult (str name-version "." java-dir ".@1"))
rule))
(defn apply-jarjar! [pname pversion]
(let [java-dirs (java-class-dirs)
name-version (clean-name-version pname pversion)
rules (map (partial create-rule name-version) java-dirs)
processor (JjMainProcessor. rules false false)
jar-file (io/file (str "target/class-deps.jar"))]
(log/info (format "prefixing %s in target/class-deps.jar with %s" java-dirs name-version))
(StandaloneJarProcessor/run jar-file jar-file processor)))
(defn remove-2parents ^String [file]
(->> (str/split (str file) #"/")
(drop 2)
(str/join "/")))
(defn mranderson-version []
(let [v (-> (io/resource "mranderson/project.clj")
slurp
edn/read-string
(nth 2))]
(assert (string? v)
(str "Something went wrong, version is not a string: " v))
v))
(defn file->extension
[file]
(re-find #"\.clj[cs]?$" file))
(defn extension->platform
[extension-of-moved]
(some->> (#{".cljs" ".clj"} extension-of-moved)
rest
(apply str)
keyword))
(defn platform-comp [platform]
(when platform
(->> #{platform}
(s/difference #{:cljs :clj})
first)))
(defn- cljfile->dir [clj-file]
(->> (str/split clj-file #"/")
butlast
(str/join "/")))
(defn duplicated-files
"Returns map of duplicates in `files`, key is fully qualified file as string, value is num occurrences.
If no duplicates, empty map is returned."
[files]
(->> files
(map #(-> % str fs/normalized str))
(frequencies)
(filterv #(> (second %) 1))
(into {})))
(defn assert-no-duplicate-files
"Throw internal error if there are any duplicates in `files`."
[files]
(let [dupes (duplicated-files files)]
(when (seq dupes)
(throw (ex-info "internal error: found unexpected duplicates in files" {:dupes dupes})))))
(defn normalize-dirs
"Returns `dirs` (as strings) normalized, deduped and without subdirs"
[dirs]
(->> dirs
(map #(-> % str fs/normalized str))
sort
distinct
(reduce (fn [ds dir]
(let [last-dir (last ds)]
(if (and last-dir (fs/child-of? last-dir dir))
ds
(conj ds dir))))
[])))
(defn clj-files->dirs
[prefix clj-files]
(->> (map cljfile->dir clj-files)
(remove str/blank?)
(map (fn [clj-dir] (str prefix "/" clj-dir)))
set))
(defn determine-source-dirs [{:keys [source-paths]
{:keys [included-source-paths]} :mranderson}]
(case included-source-paths
(nil :first) (take 1 source-paths)
:source-paths source-paths
(do
(assert (vector? included-source-paths)
(pr-str included-source-paths))
included-source-paths)))
================================================
FILE: test/mranderson/core_test.clj
================================================
(ns mranderson.core-test
(:require [mranderson.core :as sut]
[mranderson.test :refer [with-mranderson]]
[clojure.java.io :as io]
[clojure.string :as string]
[clojure.test :refer [deftest testing is]]
[me.raynes.fs :as fs])
(:import java.io.File))
;; ## Fixtures
(def dependencies
'[^:inline-dep [org.clojure/data.xml "0.2.0-alpha6"]
^:inline-dep [instaparse "1.4.12"]
^:inline-dep [riddley "0.1.12"]
^:inline-dep [cljfmt "0.7.0"]])
(def clojure-namespace-file
{:path "mrt/main.clj"
:content (str "(ns mrt.main\n"
" (:require [riddley.walk :as rw]"
" [clojure.data.xml :as xml]))")})
(def clojure-resource-file
{:path "mrt/resource.clj"
:content (pr-str [{:position {:x 1, :y 1}, :color :red}])})
;; ## Helpers
(defn- ->file
[{:keys [working-directory]} {:keys [path]}]
(io/file working-directory path))
;; ## Tests
(deftest t-resolved-tree-and-skip-java-repackage-classes
(with-mranderson
[project {:dependencies dependencies
:files [clojure-namespace-file
clojure-resource-file]
:opts {:skip-repackage-java-classes true
:unresolved-tree false}}]
(let [{:keys [working-directory prefix ns-prefix]} project]
(testing "Clojure source file was correctly updated"
(let [riddley-prefix (str ns-prefix ".riddley.v0v1v12.riddley")
xml-prefix (str ns-prefix ".dataxml.v0v2v0-alpha6.clojure.data.xml")
main-file (->file project clojure-namespace-file)]
(is (= (-> (:content clojure-namespace-file)
(string/replace "riddley" riddley-prefix)
(string/replace "clojure.data.xml" xml-prefix))
(slurp main-file)))))
(testing "Clojure resource file was not changed"
(let [resource-file (->file project clojure-resource-file)]
(is (= (:content clojure-resource-file)
(slurp resource-file)))))
(testing "Dependency source file was correctly updated"
(let [riddley-file (io/file working-directory
prefix
"riddley"
"v0v1v12"
"riddley"
"walk.clj")]
(is (string/starts-with?
(slurp riddley-file)
(str "(ns ^{:mranderson/inlined true} " ns-prefix ".riddley.v0v1v12.riddley.walk\n"
" (:refer-clojure :exclude [macroexpand])\n"
" (:require\n"
" [" ns-prefix ".riddley.v0v1v12.riddley.compiler :as cmp]))")))))
(testing "Dependency source file"
(testing "with clj extension was correctly updated"
(let [content (slurp (io/file working-directory prefix "instaparse" "v1v4v12" "instaparse" "transform.clj"))]
(is (string/starts-with? content (str "(ns " ns-prefix ".instaparse.v1v4v12.instaparse.transform")))))
(testing "with cljc extension was correctly updated"
(let [content (slurp (io/file working-directory prefix "instaparse" "v1v4v12" "instaparse" "transform.cljc"))]
(is (string/starts-with? content (str "(ns " ns-prefix ".instaparse.v1v4v12.instaparse.transform")))))))))
(deftest t-copy-source-files
(testing "Can merge files across overlapping dirs"
(let [dir-a "test-resources/a"
dir-b "test-resources/b"
intermediate-dir "same-name"
target-dir "test-resources/c/srcdeps"
filename-1 "f"
filename-2 "g"
expected-1 (io/file target-dir intermediate-dir filename-1)
expected-2 (io/file target-dir intermediate-dir filename-2)
cleanup! #(fs/delete-dir (File. target-dir))]
(cleanup!)
;; Note that both files have a different parent dir but a same intermediate dir,
;; so a correct impl will merge these dirs:
(assert (.exists (io/file dir-a intermediate-dir filename-1)))
(assert (.exists (io/file dir-b intermediate-dir filename-2)))
(assert (not (.exists expected-1)))
(assert (not (.exists expected-2)))
(try
(sut/copy-source-files [dir-a dir-b]
"test-resources/c")
(is (.exists expected-1)
(str expected-1))
(is (.exists expected-2)
(str expected-2))
(finally
(cleanup!))))))
================================================
FILE: test/mranderson/dependency_resolver_test.clj
================================================
(ns mranderson.dependency-resolver-test
(:require [clojure.test :as t]
[mranderson.dependency.resolver :as sut]
[mranderson.dependency.tree :as tree]))
(def repos
[["central" {:url "https://repo1.maven.org/maven2/"
:snapshots false}]
["clojars" {:url "https://repo.clojars.org/"}]])
(def example-dep-single
'{[mvxcvi/puget "1.0.2" :exclusions [[org.clojure/clojure]]] nil})
(def example-dep-multiple
'{[mvxcvi/puget "1.0.2" :exclusions [[org.clojure/clojure]]] nil
[fipp "0.6.10"] nil})
(def example-dep-transient-cljs
'[[cljfmt "0.6.3"]])
(defn assert-file-meta [dep _level]
(t/is (-> dep meta :file) (format "%s does not have file meta info" dep)))
(t/deftest expands-single-dep-into-unresolved-tree
(let [expanded-single (sut/expand-dep-hierarchy repos example-dep-single {})]
(t/is (= '{[mvxcvi/puget "1.0.2" :exclusions [[org.clojure/clojure]]]
{[fipp "0.6.10"] {[org.clojure/clojure "1.8.0"] nil
[org.clojure/core.rrb-vector "0.0.11"] {[org.clojure/clojure "1.5.1"] nil}}
[mvxcvi/arrangement "1.1.1"] nil}}
expanded-single)
"failed to expand mvxcvi/puget \"1.0.2\" into an unresolved dependency tree")
(tree/walk-deps expanded-single assert-file-meta)))
(t/deftest expands-multiple-deps-into-unresolved-tree
(t/is (= '{[mvxcvi/puget "1.0.2" :exclusions [[org.clojure/clojure]]]
{[fipp "0.6.10"] {[org.clojure/clojure "1.8.0"] nil,
[org.clojure/core.rrb-vector "0.0.11"] {[org.clojure/clojure "1.5.1"] nil}}
[mvxcvi/arrangement "1.1.1"] nil}
[fipp "0.6.10"] {[org.clojure/clojure "1.8.0"] nil
[org.clojure/core.rrb-vector "0.0.11"] {[org.clojure/clojure "1.5.1"] nil}}}
(sut/expand-dep-hierarchy repos example-dep-multiple {}))
"failed to expand multiple deps into an unresolved dependency tree"))
(t/deftest expand-deps-with-overriding-transient-dep
(let [expanded-w-override (sut/expand-dep-hierarchy repos example-dep-multiple '{[mvxcvi/puget fipp] [fipp "0.6.14"]})]
(t/is (= '{[mvxcvi/puget "1.0.2" :exclusions [[org.clojure/clojure]]]
{[fipp "0.6.14"] {[org.clojure/clojure "1.8.0"] nil,
[org.clojure/core.rrb-vector "0.0.13"] {[org.clojure/clojure "1.5.1"] nil}}
[mvxcvi/arrangement "1.1.1"] nil}
[fipp "0.6.10"] {[org.clojure/clojure "1.8.0"] nil
[org.clojure/core.rrb-vector "0.0.11"] {[org.clojure/clojure "1.5.1"] nil}}}
expanded-w-override)
"failed to expand multiple deps with override or override interfered with sibling dependency")
(tree/walk-deps expanded-w-override assert-file-meta)))
(t/deftest expand-deps-with-overriding-transient-dep-deep
(t/is (= '{[mvxcvi/puget "1.0.2" :exclusions [[org.clojure/clojure]]]
{[fipp "0.6.10"] {[org.clojure/clojure "1.8.0"] nil,
[org.clojure/core.rrb-vector "0.0.13"] {[org.clojure/clojure "1.5.1"] nil}}
[mvxcvi/arrangement "1.1.1"] nil}
[fipp "0.6.10"] {[org.clojure/clojure "1.8.0"] nil
[org.clojure/core.rrb-vector "0.0.11"] {[org.clojure/clojure "1.5.1"] nil}}}
(sut/expand-dep-hierarchy repos example-dep-multiple '{[mvxcvi/puget fipp org.clojure/core.rrb-vector]
[org.clojure/core.rrb-vector "0.0.13"]}))
"failed to expand multiple deps with override and override a two level deep transient dependency"))
(t/deftest cljs-and-its-dependencies-evicted
(t/is (= '{[cljfmt "0.6.3"]
{[com.googlecode.java-diff-utils/diffutils "1.3.0"] nil
[org.clojure/tools.cli "0.3.7"] nil
[org.clojure/tools.reader "1.2.2"] nil
[rewrite-cljs "0.4.4"] nil
[rewrite-clj "0.6.0"] nil}}
(tree/evict-subtrees (sut/resolve-source-deps repos example-dep-transient-cljs) '#{org.clojure/clojure org.clojure/clojurescript}))))
================================================
FILE: test/mranderson/move_test.clj
================================================
(ns mranderson.move-test
(:require [mranderson.move :as sut]
[clojure.test :as t]
[clojure.java.io :as io]
[rewrite-clj.zip :as z])
(:import [java.io File]))
(def ex-a-4
"(comment \"foobar comment here\")
(ns example.a.four)
(defn foo []
(println \"nuf said\"))
(deftype FourType [field])
(deftype FooType [])")
(def ex-5
"(ns example.five
(:import [example.with_dash.six SomeType SomeRecord]))
(defn- use-type-record []
(SomeType. :type)
(SomeRecord. :record))")
(def ex-3
"(ns example.three
(:require [example.five :as five]))
(defn use-ex-six-fully-qualified []
(example.with_dash.six.SomeType. :type)
(example.with_dash.six.SomeRecord. :record))")
(def ex-2
"(ns
^{:added \"0.0.1\"}
example.two
(:require [example.three :as three]
[example.a.four :as four]
[example.a
[foo]
[bar]])
(:import [example.a.four FourType]
example.a.four.FooType))
(defn foo []
(example.a.four/foo))
(defn cljs-foo
\"This is valid in cljs i am told.\"
[]
(example.a.four.foo))
(def delayed-four
(do
(require 'example.a.four)
(resolve 'example.a.four/foo)))
(defn my-four-type
^example.a.four.FourType
[^example.a.four.FourType t]
t)")
(def ex-1
"(ns example.one
(:require [example.two :as two]
[example.three :as three]))
(defn foo [m]
(:example.a.four/bar m)
(example.a.four/foo))")
(def ex-6-with-dash
"(ns example.with-dash.six)
(deftype SomeType [field])
(defrecord SomeRecord [field])")
(def ex-edn
"{:foo \"bar\"}")
(def ex-cljc
"(ns example.cross
#?@(:clj
[(:require [example.seven :as seven-clj])]
:cljs
[(:require [example.seven :as seven-cljs])]))")
(def ex-cljc-expected
"(ns example.cross
#?@(:clj
[(:require [example.clj.seven :as seven-clj])]
:cljs
[(:require [example.cljs.seven :as seven-cljs])]))")
(def ex-seven-clj "(ns example.seven)")
(def ex-seven-cljs "(ns example.seven)")
(def ex-data-readers
"{xml/ns clojure.data.xml.name/uri-symbol
xml/element clojure.data.xml.node/tagged-element}")
(def ex-data-readers-expected
"{xml/ns clojure.data.xml.name/uri-symbol
xml/element clojure.moved.data.xml.node/tagged-element}")
(def medley-user-example
"(ns example.user.medley
(:require [medley.core :as medley]))")
(def medley-stub "(ns medley.core)")
(def medley-stub-moved-expected "(ns ^{:inlined true} moved.medley.core)")
(def medley-user-expected
"(ns example.user.medley
(:require [moved.medley.core :as medley]))")
(def example-eight
"(ns example.eight)
(deftype EightType [])
(deftype TypeEight [])")
(def example-nine
"(ns example.nine
(:import [example.eight EightType]
example.eight.TypeEight)
(:require [example.eight :as eight]))")
(def example-nine-expected
"(ns example.nine
(:import [with_dash.example.eight EightType]
with_dash.example.eight.TypeEight)
(:require [with-dash.example.eight :as eight]))")
(def example-meta-kw1
"(ns ^:some-meta example.metakw1)")
(def example-meta-kw2
"(ns ^:some-meta example.metakw2)")
(def expected-moved-metakw1
"(ns ^:some-meta moved.metakw1)")
(def expected-moved-metakw2-watermark
"(ns ^{:some-meta true :inlined true} moved.metakw2)")
(def example-meta-map1
"(ns ^{:one 1 :zeta 42 :two 2} example.metamap1)")
(def example-meta-map2
"(ns ^{:one 1 :zeta 42 :two 2} example.metamap2)")
(def expected-moved-metamap1
"(ns ^{:one 1 :zeta 42 :two 2} moved.metamap1)")
(def expected-moved-metamap2-watermark
"(ns ^{:one 1 :zeta 42 :two 2 :inlined true} moved.metamap2)")
(defn- create-temp-dir! [dir-name]
(let [temp-file (File/createTempFile dir-name nil)]
(.delete temp-file)
(.mkdirs temp-file)
temp-file))
(defn- create-source-file! ^File [^File file ^String content]
(.delete file)
(.mkdirs (.getParentFile file))
(.createNewFile file)
(spit file content)
file)
;; this test is a slightly rewritten version of the original test for c.t.namespace.move from https://github.com/clojure/tools.namespace/blob/master/src/test/clojure/clojure/tools/namespace/move_test.clj
(t/deftest move-ns-test
(let [temp-dir (create-temp-dir! "tools-namespace-t-move-ns")
src-dir (io/file temp-dir "src")
example-dir (io/file temp-dir "src" "example")
a-dir (io/file temp-dir "src" "example" "a")
with-dash-dir (io/file temp-dir "src" "example" "with_dash")
file-one (create-source-file! (io/file example-dir "one.clj") ex-1)
file-two (create-source-file! (io/file example-dir "two.clj") ex-2)
file-three (create-source-file! (io/file example-dir "three.clj") ex-3)
old-file-four (create-source-file! (io/file a-dir "four.clj") ex-a-4)
new-file-four (io/file example-dir "b" "four.clj")
file-five (create-source-file! (io/file example-dir "five.clj") ex-5)
old-file-six (create-source-file! (io/file with-dash-dir "six.clj") ex-6-with-dash)
new-file-six (io/file example-dir "prefix" "with_dash" "six.clj")
file-edn (create-source-file! (io/file example-dir "edn.clj") ex-edn)
file-cljc (create-source-file! (io/file example-dir "cross.cljc") ex-cljc)
file-data-readers (create-source-file! (io/file example-dir "data_readers.cljc") ex-data-readers)
medley-dir (io/file src-dir "medley")
file-medley-user (create-source-file! (io/file example-dir "user" "medley.clj") medley-user-example)
file-medley-stub-moved (io/file src-dir "moved" "medley" "core.clj")
file-nine (create-source-file! (io/file example-dir "nine.clj") example-nine)
file-three-last-modified (.lastModified file-three)
file-moved-metakw1 (io/file src-dir "moved" "metakw1.clj")
file-moved-metakw2 (io/file src-dir "moved" "metakw2.clj")
file-moved-metamap1 (io/file src-dir "moved" "metamap1.clj")
file-moved-metamap2 (io/file src-dir "moved" "metamap2.clj")]
(create-source-file! (io/file example-dir "seven.clj") ex-seven-clj)
(create-source-file! (io/file example-dir "seven.cljs") ex-seven-cljs)
(create-source-file! (io/file medley-dir "core.clj") medley-stub)
(create-source-file! (io/file example-dir "eight.clj") example-eight)
(create-source-file! (io/file example-dir "metakw1.clj") example-meta-kw1)
(create-source-file! (io/file example-dir "metakw2.clj") example-meta-kw2)
(create-source-file! (io/file example-dir "metamap1.clj") example-meta-map1)
(create-source-file! (io/file example-dir "metamap2.clj") example-meta-map2)
(Thread/sleep 1500) ;; ensure file timestamps are different
(t/testing "move ns simple case, no dash, no deftype, defrecord"
(sut/move-ns 'example.a.four 'example.b.four src-dir ".clj" [src-dir] nil)
;; (println "affected after move")
;; (doseq [a [file-one file-two new-file-four]]
;; (println (.getAbsolutePath a))
;; (prn (slurp a)))
;; (println "unaffected after move")
;; (doseq [a [file-three file-edn]]
;; (println (.getAbsolutePath a))
;; (prn (slurp a)))
(t/is (.exists new-file-four)
"new file should exist")
(t/is (not (.exists old-file-four))
"old file should not exist")
(t/is (not (.exists (.getParentFile old-file-four)))
"old empty directory should not exist")
(t/is (= file-three-last-modified (.lastModified file-three))
"unaffected file should not have been modified")
(t/is (not-any? #(.contains (slurp %) "example.a.four")
[file-one file-two file-three new-file-four])
"affected files should not refer to old ns")
(t/is (.contains (slurp file-one) "(example.b.four/foo)")
"file with a reference to ns in body should refer with a symbol")
(t/is (every? #(.contains (slurp %) "example.b.four")
[file-one file-two new-file-four])
"affected files should refer to new ns")
(t/is (= 9 (count (re-seq #"example.b.four" (slurp file-two))))
"all occurances of old ns should be replace with new")
(t/is (re-find #"\(:example.b.four/" (slurp file-one))
"type of occurence is retained if keyword")
(t/is (re-find #"\[example\.b\s*\[foo\]\s*\[bar\]\]" (slurp file-two))
"prefixes should be replaced")
(t/is (= ex-data-readers (slurp file-data-readers))
"cljc file w/o ns macro is unchanged")
(t/is (= ex-edn (slurp file-edn))
"clj file wo/ ns macro is unchanged"))
(t/testing "testing import deftype no dash, dash in the prefix"
(sut/move-ns 'example.eight 'with-dash.example.eight src-dir ".clj" [src-dir] nil)
(t/is (= (slurp file-nine) example-nine-expected)))
(t/testing "move ns with dash, deftype, defrecord, import"
(sut/move-ns 'example.with-dash.six 'example.prefix.with-dash.six src-dir ".clj" [src-dir] :inlined)
(t/is (.contains (slurp new-file-six) ":inlined true")
"file that was moved should have :inlined metadata watermark")
(t/is (not-any? #(.contains (slurp %) ":inlined true")
[file-one file-two file-three file-five file-nine])
"files that were not moved should not have :inlined metadata watermark")
;; (println "affected after move")
;; (doseq [a [file-three file-five new-file-six new-file-four]]
;; (println (.getAbsolutePath a))
;; (prn (slurp a)))
(t/is (.exists new-file-six)
"new file should exist")
(t/is (not (.exists old-file-six))
"old file should not exist")
(t/is (not-any? #(.contains (slurp %) "example.with_dash.six")
[file-five file-three])
"affected files should not refer to old ns in imports or body")
(t/is (every? #(.contains (slurp %) "example.prefix.with_dash.six")
[file-five file-three])
"affected files should refer to new ns"))
(t/testing "testing cljc file using :clj/cljs macros in require depending on same ns in clj and cljs"
(sut/move-ns 'example.seven 'example.clj.seven src-dir ".clj" [src-dir] nil)
(sut/move-ns 'example.seven 'example.cljs.seven src-dir ".cljs" [src-dir] nil)
(t/is (= (slurp file-cljc) ex-cljc-expected)))
(t/testing "testing alias is first section of two section namespace"
(sut/move-ns 'medley.core 'moved.medley.core src-dir ".clj" [src-dir] :inlined)
(t/is (= (slurp file-medley-stub-moved) medley-stub-moved-expected))
(t/is (= (slurp file-medley-user) medley-user-expected)))
(t/testing "testing cljc file without ns macro, with a replacement"
(create-source-file! (io/file (io/file temp-dir "src" "clojure" "data" "xml") "node.clj") "(ns clojure.data.xml.node)")
(sut/move-ns 'clojure.data.xml.node 'clojure.moved.data.xml.node src-dir ".clj" [src-dir] nil)
(t/is (= (slurp file-data-readers) ex-data-readers-expected)))
(t/testing "namespace metadata correct on ns move"
(sut/move-ns 'example.metakw1 'moved.metakw1 src-dir ".clj" [src-dir] nil)
(t/is (= (slurp file-moved-metakw1) expected-moved-metakw1))
(sut/move-ns 'example.metakw2 'moved.metakw2 src-dir ".clj" [src-dir] :inlined)
(t/is (= (slurp file-moved-metakw2) expected-moved-metakw2-watermark))
(sut/move-ns 'example.metamap1 'moved.metamap1 src-dir ".clj" [src-dir] nil)
(t/is (= (slurp file-moved-metamap1) expected-moved-metamap1))
(sut/move-ns 'example.metamap2 'moved.metamap2 src-dir ".clj" [src-dir] :inlined)
(t/is (= (slurp file-moved-metamap2) expected-moved-metamap2-watermark)))))
(defn- s-rename-ns
"A litle helper for rename-ns-test"
[s old-ns-name new-ns-name add-meta-kw]
(-> s
z/of-string
(sut/rename-ns old-ns-name new-ns-name add-meta-kw)
z/root-string))
(t/deftest rename-ns-test
(t/is (= (s-rename-ns "(ns ^{:spam true} foo)" 'foo 'bar :mranderson/zing)
"(ns ^{:spam true :mranderson/zing true} bar)")
"adds new meta to existing meta map")
(t/is (= (s-rename-ns "(ns foo)" 'foo 'bar :mranderson/zing)
"(ns ^{:mranderson/zing true} bar)")
"adds new meta when no existing meta")
(t/is (= (s-rename-ns "(ns foo)" 'foo 'bar nil)
"(ns bar)")
"renames when no new or existing meta")
(t/is (= (s-rename-ns "(ns ^:boop foo)" 'foo 'bar nil)
"(ns ^:boop bar)")
"renames when no new meta")
(t/is (= (s-rename-ns "(ns ^:boop foo)" 'foo 'bar :mranderson/zing)
"(ns ^{:boop true :mranderson/zing true} bar)")
"renames when new meta and existing meta is kw form")
(t/is (= (s-rename-ns "(ns ^:boop foo)" 'nope 'bar :mranderson/zing)
"(ns ^:boop foo)")
"does not rename or adjust meta when old-ns-name is does not match cur ns name")
(t/is (= (s-rename-ns "(ns)" 'foo 'bar :mranderson/zing)
"(ns)")
"empty ns is unaffected")
(t/is (= (s-rename-ns "(ns #_ skipped foo)" 'foo 'bar nil)
"(ns #_ skipped bar)")
"uneval node is skipped")
(t/is (= (s-rename-ns "(ns #_#_ skip1 skip2 ^:boop #_ skip3 foo)" 'foo 'bar :mranderson/zing)
"(ns #_#_ skip1 skip2 ^{:boop true :mranderson/zing true} #_ skip3 bar)")
"uneval nodes are skipped"))
================================================
FILE: test/mranderson/test.clj
================================================
(ns mranderson.test
(:require [mranderson.core :refer [mranderson]]
[me.raynes.fs :as fs]
[clojure.java.io :as io])
(:import (java.io File)))
;; ## Fixtures
(def ^:private repositories
[["central" {:url "https://repo1.maven.org/maven2/", :snapshots false}]
["clojars" {:url "https://repo.clojars.org/"}]])
;; ## Helpers
(defmacro ^:private with-temp-dir
"Create a temporary directory, run the body, then clean up the directory."
[sym & body]
`(let [f# (doto (File/createTempFile "mranderson" "")
(.delete)
(.mkdirs))
~sym f#]
(try
(do ~@body)
(finally
(fs/delete-dir f#)))))
(defn ^:private create-files!
"Create files and return root directories that contain them."
[working-directory files]
(->> (for [{:keys [path content]} files]
(let [dirname (first (.split ^String path "/"))
target-file (io/file working-directory path)]
(.. target-file (getParentFile) (mkdirs))
(spit target-file content)
(io/file working-directory dirname)))
(distinct)
(doall)))
(defmacro ^:private with-project
"Set up a temporary directory with a structure that can be used by
mranderson."
[[sym files] & body]
`(with-temp-dir tmp#
(let [working-directory# (doto (io/file tmp# "srcdeps")
(.mkdirs))
source-directories# (create-files! working-directory# ~files)
prefix# (str "mranderson_test" (rand-int 99999))
~sym {:prefix prefix#
:ns-prefix (.replace prefix# "_" "-")
:working-directory working-directory#
:source-directories source-directories#}]
~@body)))
(defn- project->context
"Convert the given project map into a context map for mranderson."
[{:keys [^File working-directory
source-directories
prefix]}
& [opts]]
(merge
{:pprefix prefix
:pname "mranderson-test"
:pversion "0.1.0-SNAPSHOT"
:project-source-dirs source-directories
:srcdeps (.getPath working-directory)
:skip-repackage-java-classes false
:unresolved-tree false
:watermark :mranderson/inlined
:expositions nil
:overrides nil
:prefix-exclusions nil}
opts))
(defn- project->paths
"Convert the given project map into a paths map for mranderson."
[{:keys [^File working-directory prefix]}]
{:src-path (io/file working-directory prefix)
:parent-clj-dirs []
:branch []})
;; ## Run
(defn with-mranderson*
[{:keys [dependencies files opts]} f]
(with-project [project files]
(mranderson repositories
dependencies
(project->context project opts)
(project->paths project))
(f project)))
(defmacro with-mranderson
[[project {:keys [dependencies files opts] :as spec}] & body]
`(with-mranderson* ~spec (fn [~project] ~@body)))
================================================
FILE: test/mranderson/util_test.clj
================================================
(ns mranderson.util-test
(:require [mranderson.util :as util]
[me.raynes.fs :as fs]
[clojure.java.io :as io]
[clojure.test :as t]))
(t/deftest duplicated-files-test
(t/is (= {}
(util/duplicated-files [(str (fs/file "a" "b" "c"))
(str (fs/file "a" "b" "d"))]))
"no duplicates")
(t/is (= {(str (fs/file "a" "b" "c")) 2}
(util/duplicated-files [(str (fs/file "a" "b" "c"))
(str (fs/file "a" "b" "c"))]))
"exact absolute duplicates")
(t/is (= {(str (fs/file "a" "b" "c")) 3}
(util/duplicated-files [(str (fs/file "a" "b" "c"))
(str (io/file "a" "b" "c"))
(str (io/file "a" "x" ".." "b" "c"))]))
"equivalent duplicates"))
(t/deftest assert-no-duplicate-files-test
(t/is (nil? (util/assert-no-duplicate-files [(str (fs/file "a" "b" "c"))
(str (fs/file "a" "b" "d"))]))
"no throw on no dupes")
(t/is (= {:dupes {(str (fs/file "a" "b" "c")) 2}}
(try
(util/assert-no-duplicate-files [(str (fs/file "a" "b" "c"))
(str (fs/file "a" "b" "c"))])
(catch Throwable ex
(ex-data ex))))
"throw on dupes"))
(t/deftest test-normalize-dirs
(t/testing "normalizes and removes subdirs where one dir-name is a prefix of another"
(t/are [expected dirs] (t/is (= (mapv #(-> % fs/file str) expected)
(util/normalize-dirs dirs)))
["hiccup"] ["hiccup/foo" "hiccup"]
["hiccup" "hiccup2"] ["hiccup/foo" "hiccup" "hiccup2"]
["hiccup" "hiccup2"] ["hiccup/foo/.." "hiccup/bar" (str (fs/file "hiccup/bingo")) "hiccup2/bar/.."])))
(t/deftest determine-source-dirs
(t/are [desc input expected] (t/testing desc
(t/is (= expected (util/determine-source-dirs input)))
true)
"In absence of options"
{:source-paths ["src"]
:test-paths ["test"]}
["src"]
"In absence of options and multiple source-paths, takes just the first, since that's the traditional behavior"
{:source-paths ["src" "plugin"]
:test-paths ["test"]}
["src"]
"Can accept `:source-paths`"
{:source-paths ["src" "plugin"]
:test-paths ["test"]
:mranderson {:included-source-paths :source-paths}}
["src" "plugin"]
"Can accept a fixed list"
{:source-paths ["src" "plugin"]
:test-paths ["test"]
:mranderson {:included-source-paths ["plugin"]}}
["plugin"]
"Can accept a fixed list that has nothing to do with :source-paths"
{:source-paths ["src" "plugin"]
:test-paths ["test"]
:mranderson {:included-source-paths ["test" "foo"]}}
["test" "foo"]))
================================================
FILE: test-resources/a/same-name/f
================================================
================================================
FILE: test-resources/b/same-name/g
================================================
================================================
FILE: tests.edn
================================================
#kaocha/v1
{:reporter kaocha.report/documentation}
gitextract_t9wpat55/ ├── .clj-kondo/ │ ├── config.edn │ └── rewrite-clj/ │ └── rewrite-clj/ │ └── config.edn ├── .codecov.yml ├── .github/ │ └── workflows/ │ └── ci.yml ├── .gitignore ├── .gitmodules ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── README.md ├── README_old.md ├── build.sh ├── gargamel.edn ├── java-src/ │ └── mranderson/ │ └── util/ │ ├── JjMainProcessor.java │ ├── JjPackageRemapper.java │ └── JjWildcard.java ├── md-templates/ │ ├── changelog.mustache │ └── commit.mustache ├── project.clj ├── scripts/ │ └── integration_test.sh ├── src/ │ ├── leiningen/ │ │ └── inline_deps.clj │ └── mranderson/ │ ├── core.clj │ ├── dependency/ │ │ ├── resolver.clj │ │ └── tree.clj │ ├── log.clj │ ├── move.clj │ ├── plugin.clj │ ├── profiles.clj │ └── util.clj ├── test/ │ └── mranderson/ │ ├── core_test.clj │ ├── dependency_resolver_test.clj │ ├── move_test.clj │ ├── test.clj │ └── util_test.clj ├── test-resources/ │ ├── a/ │ │ └── same-name/ │ │ └── f │ └── b/ │ └── same-name/ │ └── g └── tests.edn
SYMBOL INDEX (21 symbols across 3 files)
FILE: java-src/mranderson/util/JjMainProcessor.java
class JjMainProcessor (line 31) | public class JjMainProcessor implements JarProcessor
method JjMainProcessor (line 37) | public JjMainProcessor(List<PatternElement> patterns, boolean verbose,...
method strip (line 53) | public void strip(File file) throws IOException {
method getExcludes (line 63) | private Set<String> getExcludes() {
method process (line 73) | public boolean process(EntryStruct struct) throws IOException {
FILE: java-src/mranderson/util/JjPackageRemapper.java
class JjPackageRemapper (line 33) | public class JjPackageRemapper extends Remapper
method createWildcards (line 46) | private static List<JjWildcard> createWildcards(List<? extends Pattern...
method JjPackageRemapper (line 58) | public JjPackageRemapper(List<Rule> ruleList, boolean verbose) {
method isArrayForName (line 64) | static boolean isArrayForName(String value) {
method map (line 68) | public String map(String key) {
method mapPath (line 79) | public String mapPath(String path) {
method mapValue (line 106) | public Object mapValue(Object value) {
method replaceHelper (line 141) | private String replaceHelper(String value) {
FILE: java-src/mranderson/util/JjWildcard.java
class JjWildcard (line 30) | public class JjWildcard
method JjWildcard (line 42) | public JjWildcard(String pattern, String result) {
method matches (line 102) | public boolean matches(String value) {
method replace (line 106) | public String replace(String value) {
method getMatcher (line 117) | private Matcher getMatcher(String value) {
method checkIdentifierChars (line 124) | private static boolean checkIdentifierChars(String expr, String extra) {
method replaceAllLiteral (line 141) | private static String replaceAllLiteral(Pattern pattern, String value,...
method toString (line 146) | public String toString() {
Condensed preview — 37 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (141K chars).
[
{
"path": ".clj-kondo/config.edn",
"chars": 271,
"preview": "{:config-paths ^:replace [] ;; don't pick up user defaults\n :lint-as\n {mranderson.test/with-mranderson clojure.core/let\n"
},
{
"path": ".clj-kondo/rewrite-clj/rewrite-clj/config.edn",
"chars": 186,
"preview": "{:lint-as\n {rewrite-clj.zip/subedit-> clojure.core/->\n rewrite-clj.zip/subedit->> clojure.core/->>\n rewrite-clj.zip/ed"
},
{
"path": ".codecov.yml",
"chars": 145,
"preview": "coverage:\n status:\n project:\n default:\n informational: true\n patch:\n default:\n informatio"
},
{
"path": ".github/workflows/ci.yml",
"chars": 1394,
"preview": "name: CI\n\non:\n push:\n branches: [master]\n pull_request:\n branches: [master]\n\njobs:\n build:\n runs-on: ubuntu-"
},
{
"path": ".gitignore",
"chars": 150,
"preview": "/target\n/classes\n/checkouts\n/test-resources/c\npom.xml\npom.xml.asc\n*.jar\n*.class\n/.lein-*\n/.nrepl-port\n*~\n/.clj-kondo/.ca"
},
{
"path": ".gitmodules",
"chars": 273,
"preview": "[submodule \"test-resources/cider-nrepl\"]\n\tpath = test-resources/cider-nrepl\n\turl = https://github.com/clojure-emacs/cide"
},
{
"path": "CHANGELOG.md",
"chars": 5993,
"preview": "# Changelog\n\n## Unreleased\n\n\n## Bug fixes\n\n- [fix [#78](https://github.com/benedekfazekas/mranderson/issues/78)] Fix wat"
},
{
"path": "LICENSE",
"chars": 11220,
"preview": "THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC\nLICENSE (\"AGREEMENT\"). ANY USE, REPRODUCTION"
},
{
"path": "Makefile",
"chars": 721,
"preview": ".PHONY: install inline test integration-test deploy clean\n\n# Note that `install` is a two-step process: given that mrand"
},
{
"path": "README.md",
"chars": 13280,
"preview": "[](https://github.com/benedekfazek"
},
{
"path": "README_old.md",
"chars": 4146,
"preview": "[](https://circleci.com/gh/benede"
},
{
"path": "build.sh",
"chars": 305,
"preview": "#!/bin/bash\n\n# runs source-deps and tests and then provided lein target with mranderson\n\nfunction check_result {\n if "
},
{
"path": "gargamel.edn",
"chars": 699,
"preview": "{:sections\n [{:key :fixes :regex \".*#\\\\d+.*\" :title \"New features, bugfixes\"}\n {:key :wip :regex \".*\\\\[[Ww][iI][pP]\\\\]."
},
{
"path": "java-src/mranderson/util/JjMainProcessor.java",
"chars": 2903,
"preview": "/**\n * Copied from Jar Jar Links 1.4\n *\n * Original licence\n *\n * Copyright 2007 Google Inc.\n *\n * Licensed under the Ap"
},
{
"path": "java-src/mranderson/util/JjPackageRemapper.java",
"chars": 5124,
"preview": "/**\n * Copied from Jar Jar Links 1.4\n *\n * Original licence\n *\n * Copyright 2007 Google Inc.\n *\n * Licensed under the Ap"
},
{
"path": "java-src/mranderson/util/JjWildcard.java",
"chars": 5464,
"preview": "/**\n * Copied from Jar Jar Links 1.4\n *\n * Original licence\n *\n * Copyright 2007 Google Inc.\n *\n * Copyright 2007 Google"
},
{
"path": "md-templates/changelog.mustache",
"chars": 554,
"preview": "{{^to-build-num}}\n# Changelog for [{{to}}]({{to-url}})\n{{/to-build-num}}\n{{#to-build-num}}\n# Changelog for build [{{to-b"
},
{
"path": "md-templates/commit.mustache",
"chars": 109,
"preview": "\n* [{{hash}}]({{url}}) (*{{project-name}}*) {{date}} [{{commiter}}] **{{linked-subject}}**\n\n {{linked-body}}"
},
{
"path": "project.clj",
"chars": 2593,
"preview": "(def project-version \"0.5.4-SNAPSHOT\")\n\n(defproject thomasa/mranderson project-version\n :description \"Dependency inlini"
},
{
"path": "scripts/integration_test.sh",
"chars": 644,
"preview": "#!/usr/bin/env bash\nset -Eeuxo pipefail\ncd \"$(git rev-parse --show-toplevel)\"\n\n# An integration test that exercises that"
},
{
"path": "src/leiningen/inline_deps.clj",
"chars": 3331,
"preview": "(ns leiningen.inline-deps\n (:require [clojure.edn :as edn]\n [me.raynes.fs :as fs]\n [mranderson.co"
},
{
"path": "src/mranderson/core.clj",
"chars": 18085,
"preview": "(ns mranderson.core\n (:require [clojure.java.io :as io]\n [clojure.string :as str]\n [clojure.tools"
},
{
"path": "src/mranderson/dependency/resolver.clj",
"chars": 816,
"preview": "(ns mranderson.dependency.resolver\n (:require [cemerick.pomegranate.aether :as aether]\n [mranderson.dependen"
},
{
"path": "src/mranderson/dependency/tree.clj",
"chars": 4015,
"preview": "(ns mranderson.dependency.tree\n (:require [clojure.tools.namespace.dependency :as dep]))\n\n;; inlined from leiningen sou"
},
{
"path": "src/mranderson/log.clj",
"chars": 1212,
"preview": "(ns ^:no-doc mranderson.log\n \"Here we replicate lein logging.\n This gives us what a lein plugin user would expect and "
},
{
"path": "src/mranderson/move.clj",
"chars": 14853,
"preview": ";; Copyright (c) Stuart Sierra, 2012. All rights reserved. The use and\n;; distribution terms for this software are cover"
},
{
"path": "src/mranderson/plugin.clj",
"chars": 832,
"preview": "(ns mranderson.plugin\n (:require [mranderson.util :refer [first-src-path clojure-source-files source-dep?]]\n "
},
{
"path": "src/mranderson/profiles.clj",
"chars": 299,
"preview": "{:config ^:leaky {:omit-source true\n :source-paths [\"target/srcdeps\"]\n :filespecs [{:t"
},
{
"path": "src/mranderson/util.clj",
"chars": 6076,
"preview": "(ns mranderson.util\n (:require [clojure.edn :as edn]\n [clojure.java.io :as io]\n [clojure.string :"
},
{
"path": "test/mranderson/core_test.clj",
"chars": 4601,
"preview": "(ns mranderson.core-test\n (:require [mranderson.core :as sut]\n [mranderson.test :refer [with-mranderson]]\n "
},
{
"path": "test/mranderson/dependency_resolver_test.clj",
"chars": 4340,
"preview": "(ns mranderson.dependency-resolver-test\n (:require [clojure.test :as t]\n [mranderson.dependency.resolver :as"
},
{
"path": "test/mranderson/move_test.clj",
"chars": 13439,
"preview": "(ns mranderson.move-test\n (:require [mranderson.move :as sut]\n [clojure.test :as t]\n [clojure.jav"
},
{
"path": "test/mranderson/test.clj",
"chars": 3241,
"preview": "(ns mranderson.test\n (:require [mranderson.core :refer [mranderson]]\n [me.raynes.fs :as fs]\n [clo"
},
{
"path": "test/mranderson/util_test.clj",
"chars": 2932,
"preview": "(ns mranderson.util-test\n (:require [mranderson.util :as util]\n [me.raynes.fs :as fs]\n [clojure.j"
},
{
"path": "test-resources/a/same-name/f",
"chars": 0,
"preview": ""
},
{
"path": "test-resources/b/same-name/g",
"chars": 0,
"preview": ""
},
{
"path": "tests.edn",
"chars": 51,
"preview": "#kaocha/v1\n{:reporter kaocha.report/documentation}\n"
}
]
About this extraction
This page contains the full source code of the benedekfazekas/mranderson GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 37 files (131.1 KB), approximately 35.6k tokens, and a symbol index with 21 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.