├── .github
├── ISSUE_TEMPLATE.md
├── PULL_REQUEST_TEMPLATE.md
└── workflows
│ └── build.yml
├── .gitignore
├── .travis.yml
├── CLA.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── appveyor.yml
├── buckaroo-cli
├── .gitignore
├── Program.fs
├── buckaroo-cli.fsproj
└── nuget.config
├── buckaroo-tests
├── .gitignore
├── Bash.fs
├── Command.fs
├── Constraint.fs
├── Dependency.fs
├── Git.fs
├── Glob.fs
├── Lock.fs
├── Manifest.fs
├── PackageIdentifier.fs
├── Paths.fs
├── Program.fs
├── SemVer.fs
├── Solver.fs
├── Target.fs
├── Version.fs
└── buckaroo-tests.fsproj
├── buckaroo.sln
├── buckaroo
├── .gitignore
├── AddCommand.fs
├── Archive.fs
├── ArchiveType.fs
├── Atom.fs
├── Bash.fs
├── BitBucketApi.fs
├── BuckConfig.fs
├── BuildSystem.fs
├── Command.fs
├── ConsoleManager.fs
├── Constants.fs
├── Constraint.fs
├── DefaultSourceExplorer.fs
├── Dependency.fs
├── DownloadManager.fs
├── ExplainCommand.fs
├── Files.fs
├── Git.fs
├── GitCli.fs
├── GitHubApi.fs
├── GitLabApi.fs
├── GitLib.fs
├── GitManager.fs
├── Glob.fs
├── Hashing.fs
├── HelpCommand.fs
├── InstallCommand.fs
├── Lock.fs
├── Logger.fs
├── Manifest.fs
├── Option.fs
├── Override.fs
├── OverrideSourceExplorer.fs
├── PackageIdentifier.fs
├── PackageLocation.fs
├── PackageLock.fs
├── PackageSource.fs
├── Paths.fs
├── Prefetch.fs
├── QuickstartCommand.fs
├── RemoveCommand.fs
├── Resolution.fs
├── ResolveCommand.fs
├── ResolvedVersion.fs
├── Result.fs
├── RichOutput.fs
├── SearchStrategy.fs
├── SemVer.fs
├── ShowCompletions.fs
├── Solver.fs
├── SourceExplorer.fs
├── StartCommand.fs
├── Strings.fs
├── Target.fs
├── TargetIdentifier.fs
├── Tasks.fs
├── Telemetry.fs
├── Toml.fs
├── UpgradeCommand.fs
├── Version.fs
├── VersionCommand.fs
└── buckaroo.fsproj
├── buckaroo_bash_completion.bash
├── warp-bundle-linux.sh
├── warp-bundle-macos.sh
├── warp-bundle-windows.ps1
└── www
├── VersionStream.svg
├── git.png
├── how-buckaroo-works.png
├── how-buckaroo-works.svg
├── ides.png
├── ides.svg
├── logo-medium.png
├── logo.png
├── packages.png
├── registries.png
├── registries.svg
└── usual-suspects.png
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## Description
4 |
5 |
6 | ## Expected Behavior
7 |
8 |
9 | ## Actual Behavior
10 |
11 |
12 | ## Possible Fix
13 |
14 |
15 | ## Steps to Reproduce
16 |
17 |
18 | 1.
19 | 2.
20 | 3.
21 | 4.
22 |
23 | ## Context
24 |
25 |
26 | ## Your Environment
27 |
28 | * Version used:
29 | * Operating System and Architecture:
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## Description
4 |
5 |
6 | ## Related Issue
7 |
8 |
9 |
10 |
11 |
12 | ## Motivation and Context
13 |
14 |
15 | ## How Has This Been Tested?
16 |
17 |
18 |
19 |
20 | ## Screenshots (if appropriate):
21 |
22 | ## Types of changes
23 |
24 | - [ ] Bug fix (non-breaking change which fixes an issue)
25 | - [ ] New feature (non-breaking change which adds functionality)
26 | - [ ] Breaking change (fix or feature that would cause existing functionality to change)
27 |
28 | ## Checklist:
29 |
30 |
31 | - [ ] My code follows the code style of this project.
32 | - [ ] My change requires a change to the documentation.
33 | - [ ] I have updated the documentation accordingly.
34 | - [ ] I have read the **CONTRIBUTING** document.
35 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Build
2 |
3 | on: [ push, pull_request ]
4 |
5 | jobs:
6 |
7 | build:
8 | runs-on: ubuntu-20.04
9 |
10 | steps:
11 | - name: Setup .NET Core SDK
12 | uses: actions/setup-dotnet@v1.7.2
13 | with:
14 | dotnet-version: 6.0.x
15 |
16 | - uses: actions/checkout@v2
17 |
18 | - name: Build and test
19 | env:
20 | DOTNET_CLI_TELEMETRY_OPTOUT: true
21 | DOTNET_NOLOGO: true
22 | run: |
23 | dotnet build
24 | dotnet test
25 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /buckaroo.txt
2 | /buckaroo.lock.txt
3 | /buckaroo.toml
4 | /buckaroo.lock.toml
5 | /BUCKAROO_DEPS
6 |
7 | /buckaroo-packages
8 |
9 | /test/
10 | /cache/
11 |
12 | /.buckd/
13 | /buck-out/
14 | .buckconfig.local
15 |
16 | fsi.fsx
17 | /.ionide
18 |
19 | ## Ignore Visual Studio temporary files, build results, and
20 | ## files generated by popular Visual Studio add-ons.
21 |
22 | # User-specific files
23 | *.suo
24 | *.user
25 | *.userosscache
26 | *.sln.docstates
27 |
28 | # User-specific files (MonoDevelop/Xamarin Studio)
29 | *.userprefs
30 |
31 | # Build results
32 | [Dd]ebug/
33 | [Dd]ebugPublic/
34 | [Rr]elease/
35 | [Rr]eleases/
36 | x64/
37 | x86/
38 | bld/
39 | [Bb]in/
40 | [Oo]bj/
41 | [Ll]og/
42 |
43 | # Visual Studio 2015 cache/options directory
44 | .vs/
45 | # Uncomment if you have tasks that create the project's static files in wwwroot
46 | #wwwroot/
47 |
48 | # MSTest test Results
49 | [Tt]est[Rr]esult*/
50 | [Bb]uild[Ll]og.*
51 |
52 | # NUNIT
53 | *.VisualState.xml
54 | TestResult.xml
55 |
56 | # Build Results of an ATL Project
57 | [Dd]ebugPS/
58 | [Rr]eleasePS/
59 | dlldata.c
60 |
61 | # DNX
62 | project.lock.json
63 | project.fragment.lock.json
64 | artifacts/
65 |
66 | *_i.c
67 | *_p.c
68 | *_i.h
69 | *.ilk
70 | *.meta
71 | *.obj
72 | *.pch
73 | *.pdb
74 | *.pgc
75 | *.pgd
76 | *.rsp
77 | *.sbr
78 | *.tlb
79 | *.tli
80 | *.tlh
81 | *.tmp
82 | *.tmp_proj
83 | *.log
84 | *.vspscc
85 | *.vssscc
86 | .builds
87 | *.pidb
88 | *.svclog
89 | *.scc
90 |
91 | # Chutzpah Test files
92 | _Chutzpah*
93 |
94 | # Visual C++ cache files
95 | ipch/
96 | *.aps
97 | *.ncb
98 | *.opendb
99 | *.opensdf
100 | *.sdf
101 | *.cachefile
102 | *.VC.db
103 | *.VC.VC.opendb
104 |
105 | # Visual Studio profiler
106 | *.psess
107 | *.vsp
108 | *.vspx
109 | *.sap
110 |
111 | # TFS 2012 Local Workspace
112 | $tf/
113 |
114 | # Guidance Automation Toolkit
115 | *.gpState
116 |
117 | # ReSharper is a .NET coding add-in
118 | _ReSharper*/
119 | *.[Rr]e[Ss]harper
120 | *.DotSettings.user
121 |
122 | # JustCode is a .NET coding add-in
123 | .JustCode
124 |
125 | # TeamCity is a build add-in
126 | _TeamCity*
127 |
128 | # DotCover is a Code Coverage Tool
129 | *.dotCover
130 |
131 | # Visual Studio code coverage results
132 | *.coverage
133 | *.coveragexml
134 |
135 | # NCrunch
136 | _NCrunch_*
137 | .*crunch*.local.xml
138 | nCrunchTemp_*
139 |
140 | # MightyMoose
141 | *.mm.*
142 | AutoTest.Net/
143 |
144 | # Web workbench (sass)
145 | .sass-cache/
146 |
147 | # Installshield output folder
148 | [Ee]xpress/
149 |
150 | # DocProject is a documentation generator add-in
151 | DocProject/buildhelp/
152 | DocProject/Help/*.HxT
153 | DocProject/Help/*.HxC
154 | DocProject/Help/*.hhc
155 | DocProject/Help/*.hhk
156 | DocProject/Help/*.hhp
157 | DocProject/Help/Html2
158 | DocProject/Help/html
159 |
160 | # Click-Once directory
161 | publish/
162 |
163 | # Publish Web Output
164 | *.[Pp]ublish.xml
165 | *.azurePubxml
166 | *.pubxml
167 | *.publishproj
168 |
169 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
170 | # checkin your Azure Web App publish settings, but sensitive information contained
171 | # in these scripts will be unencrypted
172 | PublishScripts/
173 |
174 | # NuGet Packages
175 | *.nupkg
176 | # The packages folder can be ignored because of Package Restore
177 | **/packages/*
178 | # except build/, which is used as an MSBuild target.
179 | !**/packages/build/
180 | # Uncomment if necessary however generally it will be regenerated when needed
181 | #!**/packages/repositories.config
182 | # NuGet v3's project.json files produces more ignoreable files
183 | *.nuget.props
184 | *.nuget.targets
185 |
186 | # Microsoft Azure Build Output
187 | csx/
188 | *.build.csdef
189 |
190 | # Microsoft Azure Emulator
191 | ecf/
192 | rcf/
193 |
194 | # Windows Store app package directories and files
195 | AppPackages/
196 | BundleArtifacts/
197 | Package.StoreAssociation.xml
198 | _pkginfo.txt
199 |
200 | # Visual Studio cache files
201 | # files ending in .cache can be ignored
202 | *.[Cc]ache
203 | # but keep track of directories ending in .cache
204 | !*.[Cc]ache/
205 |
206 | # Others
207 | ClientBin/
208 | ~$*
209 | *~
210 | *.dbmdl
211 | *.dbproj.schemaview
212 | *.jfm
213 | *.pfx
214 | *.publishsettings
215 | node_modules/
216 | orleans.codegen.cs
217 |
218 | # Since there are multiple workflows, uncomment next line to ignore bower_components
219 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
220 | #bower_components/
221 |
222 | # RIA/Silverlight projects
223 | Generated_Code/
224 |
225 | # Backup & report files from converting an old project file
226 | # to a newer Visual Studio version. Backup files are not needed,
227 | # because we have git ;-)
228 | _UpgradeReport_Files/
229 | Backup*/
230 | UpgradeLog*.XML
231 | UpgradeLog*.htm
232 |
233 | # SQL Server files
234 | *.mdf
235 | *.ldf
236 |
237 | # Business Intelligence projects
238 | *.rdl.data
239 | *.bim.layout
240 | *.bim_*.settings
241 |
242 | # Microsoft Fakes
243 | FakesAssemblies/
244 |
245 | # GhostDoc plugin setting file
246 | *.GhostDoc.xml
247 |
248 | # Node.js Tools for Visual Studio
249 | .ntvs_analysis.dat
250 |
251 | # Visual Studio 6 build log
252 | *.plg
253 |
254 | # Visual Studio 6 workspace options file
255 | *.opt
256 |
257 | # Visual Studio LightSwitch build output
258 | **/*.HTMLClient/GeneratedArtifacts
259 | **/*.DesktopClient/GeneratedArtifacts
260 | **/*.DesktopClient/ModelManifest.xml
261 | **/*.Server/GeneratedArtifacts
262 | **/*.Server/ModelManifest.xml
263 | _Pvt_Extensions
264 |
265 | # Paket dependency manager
266 | .paket/paket.exe
267 | paket-files/
268 |
269 | # FAKE - F# Make
270 | .fake/
271 |
272 | # JetBrains Rider
273 | .idea/
274 | *.sln.iml
275 |
276 | # CodeRush
277 | .cr/
278 |
279 | # Python Tools for Visual Studio (PTVS)
280 | __pycache__/
281 | *.pyc
282 |
283 | # Cake - Uncomment if you are using it
284 | # tools/
285 |
286 | /cache
287 |
288 | warp-packer
289 | warp-packer.exe
290 | buckaroo-macos
291 | buckaroo-linux
292 | buckaroo-windows.exe
293 | warp/
294 |
295 | fsi.fsx
296 |
297 | .ionide
298 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: csharp
2 | solution: buckaroo.sln
3 | mono: none
4 | dotnet: 2.1.500
5 |
6 | addons:
7 | apt:
8 | packages:
9 | - hello
10 | - libcurl4-gnutls-dev
11 | homebrew:
12 | packages:
13 | - hello
14 |
15 | os:
16 | - linux
17 | - osx
18 |
19 | deploy:
20 | provider: releases
21 | api_key:
22 | secure: fKnFdbsFZqotefPp0VrAN3CnUWsjizAs0Jnr71ZhrAX6yv82bJwa0sy3TPdWeGQcMEGWaEZYU/t1ovZ2NX1uVrkNXv9BI1GMiJdkM7IVVZ1tUYskmfr9Xvtgr84AVJbsKu775r84jUQ3/wNw2FeDBvvPbZ/1WM1nN4XaKup5rpjIU01uc1Xyzz4PhuaT4LXP7lQ82uV838cDGFn2le9AKtCLlcI5+fe6/ttjGm+46m32xSIwwbuzFNdO/Epw4TEq+VbFeoRSKmxENXa70I4CXxqaKcmSxvS5yRmQQENjNYqNXmI6Gx/9Ro7lJMni7P7jPGyh5G4G/s9UEhHefklecIgsq/eXuGprN5NcBJIOvZP5dReMR/9zAonzI4qS0V2/igoZau5VEH/twhCA2nOk4pTLRFsLbBibs2JmT6WTbAMsJuliae4EvivdQ+b99TphxTJia3EPTd2THwOfvHJ1zEc23Vgd7B7j5laVE9G553i03U/d7Cc3KNDt7yz+VDOVxm1f2CxhD1KpmQ93TDe42oNs96bcABtEQyi7IJZNz8zClBSyF1e1HwdImNojlYHnT5EkguVNpCm45dVcecHLGTvlWTkNY6J+6Br6s6BEGWTiEZDHngsi0PJ4S8La1wBJ0UGdHfxvijtAmqfCZoRUPLwDkIb/meZ7Eav/VYTIu4A=
23 | file_glob: true
24 | file:
25 | - ./warp/buckaroo-*
26 | skip_cleanup: true
27 | on:
28 | repo: LoopPerfect/buckaroo
29 | tags: true
30 | all_branches: true
31 |
32 | script:
33 | - dotnet build ./buckaroo
34 | - dotnet build ./buckaroo-cli
35 | - dotnet test ./buckaroo-tests
36 | - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then ./warp-bundle-macos.sh ; fi
37 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then ./warp-bundle-linux.sh ; fi
38 |
--------------------------------------------------------------------------------
/CLA.md:
--------------------------------------------------------------------------------
1 | # Buckaroo contributor agreement
2 |
3 | The Buckaroo Agreement (this **"Agreement"**) applies to any Contribution you make to any Work.
4 |
5 | This is a binding legal agreement on you and any organization you represent. If you are signing this Agreement on behalf of your employer or other organization, you represent and warrant that you have the authority to agree to this Agreement on behalf of the organization.
6 |
7 |
8 | ## 1. Definitions
9 |
10 | **"Contribution"** means any original work, including any modification of or addition to an existing work, that you submit to Buckaroo in any manner for inclusion in any Work.
11 |
12 | **"Buckaroo", "we"** and **"use"** means LoopPerfect Ltd.
13 |
14 | **"Work"** means any project, work or materials owned or managed by LoopPerfect Ltd.
15 |
16 | **"You"** and **"your"** means you and any organization on whose behalf you are entering this Agreement.
17 |
18 |
19 | ## 2. Copyright Assignment, License and Waiver
20 |
21 | **(a) Assignment.** By submitting a Contribution, you assign to Buckaroo all right, title and interest in any copyright you have in the Contribution, and you waive any rights, including any moral rights, database rights, etc., that may affect your ownership of the copyright in the Contribution.
22 |
23 | **(b) License to Buckaroo.** If your assignment in Section 2(a) is ineffective for any reason, you grant to us and to any recipient of any Work distributed by use, a perpetual, worldwide, transferable, non-exclusive, no-charge, royalty-free, irrevocable, and sub-licensable licence to use, reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute Contributions and any derivative work created based on a Contribution. If your license grant is ineffective for any reason, you irrevocably waive and covenant to not assert any claim you may have against us, our successors in interest, and any of our direct or indirect licensees and customers, arising out of our, our successors in interest's, or any of our direct or indirect licensees' or customers' use, reproduction, preparation of derivative works, public display, public performance, sublicense, and distribution of a Contribution. You also agree that we may publicly use your name and the name of any organization on whose behalf you're entering into this Agreement in connection with publicizing the Work.
24 |
25 | **(c) License to you.** We grant to you a perpetual, worldwide, transferable, non-exclusive, no-charge, royalty-free, irrevocable, and sub-licensable license to use, reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute a Contribution and any derivative works you create based on a Contribution.
26 |
27 |
28 | ## 3. Patent License
29 | You grant to us and to any recipient of any Work distributed by us, a perpetual, worldwide, transferable, non-exclusive, no-charge, royalty-free, irrevocable, and sub-licensable patent license to make, have made, use, sell, offer to sell, import, and otherwise transfer the Contribution in whole or in part, along or included in any Work under any patent you own, or license from a third party, that is necessarily infringed by the Contribution or by combination of the Contribution with any Work.
30 |
31 |
32 | ## 4. Your Representation and Warranties.
33 | By submitting a Contribution, you represent and warrant that: (a) each Contribution you submit is an original work and you can legally grant the rights set out in this Agreement; (b) the Contribution does not, and any exercise of the rights granted by you will not, infringe any third party's intellectual property or other right; and (c) you are not aware of any claims, suits, or actions pertaining to the Contribution. You will notify us immediately if you become aware or have reason to believe that any of your representations and warranties is or becomes inaccurate.
34 |
35 |
36 | ## 5. Intellectual Property
37 | Except for the assignment and licenses set forth in this Agreement, this Agreement does not transfer any right, title or interest in any intellectual property right of either party to the other. If you choose to provide us with suggestions, ideas for improvement, recommendations or other feedback, on any Work we may use your feedback without any restriction or payment.
38 |
39 |
40 | ## Miscellaneous
41 | English law governs this Agreement, excluding any applicable conflict of laws rules or principles, and the parties agree to the exclusive jurisdiction of the courts in England, UK. This Agreement does not create a partnership, agency relationship, or joint venture between the parties. We may assign this Agreement without notice or restriction. If any provision of this Agreement is unenforcable, that provision will be modified to render it enforceable to the extent possible to effect the parties' intention and the remaining provisions will not be affected. The parties may amend this Agreement only in a written amendment signed by both parties. This Agreement comprises the parties' entire agreement relating to the subject matter of this Agreement.
42 |
43 | **Agreed and accepted on my behalf and on behalf of my organization**
44 |
45 | Our contributor agreement is based on the [mongoDB contributor agreement](https://www.mongodb.com/legal/contributor-agreement).
46 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
6 |
7 | ## Our Standards
8 |
9 | Examples of behavior that contributes to creating a positive environment include:
10 |
11 | * Using welcoming and inclusive language
12 | * Being respectful of differing viewpoints and experiences
13 | * Gracefully accepting constructive criticism
14 | * Focusing on what is best for the community
15 | * Showing empathy towards other community members
16 |
17 | Examples of unacceptable behavior by participants include:
18 |
19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances
20 | * Trolling, insulting/derogatory comments, and personal or political attacks
21 | * Public or private harassment
22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission
23 | * Other conduct which could reasonably be considered inappropriate in a professional setting
24 |
25 | ## Our Responsibilities
26 |
27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
28 |
29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
30 |
31 | ## Scope
32 |
33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
34 |
35 | ## Enforcement
36 |
37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at hello@buckaroo.pm. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
38 |
39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
40 |
41 | ## Attribution
42 |
43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
44 |
45 | [homepage]: http://contributor-covenant.org
46 | [version]: http://contributor-covenant.org/version/1/4/
47 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to Buckaroo 🎉
2 |
3 | Hello! Thanks for taking the time to contribute to Buckaroo. Buckaroo is a community project, and will only succeed through the work of contributors such as yourself. This guide will help you get started.
4 |
5 |
6 | ## Important Resources
7 |
8 | The Buckaroo project is spread across a few different repositories:
9 |
10 | 1. [LoopPerfect/buckaroo](https://github.com/LoopPerfect/buckaroo) contains the source-code for the Buckaroo client that users install on their machines.
11 | 2. [LoopPerfect/buckaroo-wishlist](https://github.com/LoopPerfect/buckaroo-wishlist) is an issue-tracker where users can suggest recipes for inclusion in the official cookbook.
12 | 3. [LoopPerfect/homebrew-lp](https://github.com/LoopPerfect/homebrew-lp) is the [Homebrew](https://brew.sh/) (and [Linuxbrew](http://linuxbrew.sh/)) tap that contains the Buckaroo formula.
13 |
14 | Official packages live in a [dedicated GitHub account](https://github.com/buckaroo-pm/).
15 |
16 |
17 | ## Getting Help
18 |
19 | If you need quick interaction, a good avenue for talking to the developers is [Gitter.im](https://gitter.im/LoopPerfect/buckaroo). If you have a complex problem, reporting an issue is the best route.
20 |
21 |
22 | ## Reporting Issues
23 |
24 | Since Buckaroo has a few different components, it is important that issues are reported in the right place.
25 |
26 | * Report **installation problems** to the [buckaroo issue tracker](https://github.com/LoopPerfect/buckaroo/issues).
27 | * Report **bugs in the client** to the [buckaroo issue tracker](https://github.com/LoopPerfect/buckaroo/issues).
28 | * Report **issues with a specific package** to that package's issue tracker.
29 | * Report **package requests** to the [buckaroo-wishlist](https://github.com/LoopPerfect/buckaroo-wishlist/issues).
30 | * Report **feature requests** to the [buckaroo issue tracker](https://github.com/LoopPerfect/buckaroo/issues).
31 |
32 | If you are not sure, just report to the [buckaroo issue tracker](https://github.com/LoopPerfect/buckaroo/issues). 😌
33 |
34 |
35 | ### Security
36 |
37 | If you have a sensitive issue, such as a security bug, please send an email to security@buckaroo.pm.
38 |
39 |
40 | ## Making a Contribution
41 |
42 | The procedure for contributing to Buckaroo is:
43 |
44 | 1. Fork [LoopPerfect/buckaroo](https://github.com/LoopPerfect/buckaroo) on GitHub
45 | 2. Make some changes, adding unit-tests where appropriate
46 | 3. Ensure that all tests pass
47 | 4. Make a pull request (usually to `master`)
48 | 5. Bask in the kudos! 👏👑
49 |
50 |
51 | ## What should I work on?
52 |
53 | We endeavor to keep [the issue tracker](https://github.com/LoopPerfect/buckaroo/issues) up-to-date, so that is the best place to start. Keep an eye out for [issues marked "help wanted"](https://github.com/LoopPerfect/buckaroo/labels/help%20wanted).
54 |
55 | ### First-time Contributor ❤️
56 |
57 | First-time contributor? Take a look at the issue tracker for [issues marked "first commit"](https://github.com/LoopPerfect/buckaroo/labels/first%20commit) for smaller, self-contained tasks. We would also be happy to walk you through any of the existing code on [Gitter.im](https://gitter.im/LoopPerfect/buckaroo).
58 |
59 | ### Packages
60 |
61 | Another way to contribute is by writing packages! Because Buckaroo is decentralized, you can do this in your repo without approvals from us. However, we can also help guide the process on [Gitter.im](https://gitter.im/LoopPerfect/buckaroo).
62 |
63 | If you are looking for a library to port, [the wishlist](https://github.com/LoopPerfect/buckaroo-wishlist) is a good place to start.
64 |
65 | ### Your Own Feature
66 |
67 | If you would like to contribute a feature that you have thought of, please [create an issue](https://github.com/LoopPerfect/buckaroo/issues) first so that we can ensure the design is in-keeping with the general direction of Buckaroo.
68 |
69 | ## Environment
70 |
71 | Development of Buckaroo requires [F# and dotNET Core](https://docs.microsoft.com/en-us/dotnet/fsharp/get-started/get-started-command-line). We also recommend [Visual Studio Code](https://code.visualstudio.com/) and [Ionide](http://ionide.io/) for code-completion features.
72 |
73 | To fetch the source-code:
74 |
75 | ```bash=
76 | git clone https://github.com/LoopPerfect/buckaroo.git
77 | cd buckaroo
78 | ```
79 |
80 | To build the project:
81 |
82 | ```bash=
83 | dotnet build ./buckaroo-cli
84 | ```
85 |
86 | To run the project:
87 |
88 | ```bash=
89 | dotnet run --project ./buckaroo-cli
90 | ```
91 |
92 | To pass arguments to Buckaroo using `dotnet`, use `--`:
93 |
94 | ```bash=
95 | dotnet run --project ./buckaroo-cli -- version
96 | ```
97 |
98 |
99 | ### Which branch should I use? 🤔
100 |
101 | The convention for branches in Buckaroo is:
102 |
103 | * `master` - the very latest code with the latest features, but not recommended for production use
104 | * `release/version` - the branch from which a release version is tagged
105 | * `bugfix/bug` - a branch for fixing a specific bug
106 | * `feature/widget` - a branch for implementing a specific feature
107 | * `improvement/widget` - a branch for making a specific improvement, such as refactoring
108 |
109 | Most developers should branch from `master` in order to have their changes included in the next release.
110 |
111 | If you would like to patch an old release, you should branch off of `release/version`.
112 |
113 |
114 | ## Testing
115 |
116 | Buckaroo uses automated testing and [Travis CI](https://travis-ci.org/LoopPerfect/buckaroo) to prevent regressions.
117 |
118 | To run the tests:
119 |
120 | ```bash=
121 | dotnet test
122 | ```
123 |
124 | ## Bundling
125 |
126 | Releases are bundled using [Warp](https://github.com/dgiagio/warp):
127 |
128 | ```bash=
129 | wget -O warp-packer https://github.com/dgiagio/warp/releases/download/v0.3.0/macos-x64.warp-packer
130 | ./warp-packer
131 | ```
132 |
133 | To create a release for macOS:
134 |
135 | ```bash=
136 | dotnet publish ./buckaroo-cli/ -c Release -r osx-x64
137 | ./warp-packer --arch macos-x64 --exec buckaroo-cli --input_dir ./buckaroo-cli/bin/Release/netcoreapp2.1/osx-x64 --output buckaroo-macos
138 | ./buckaroo-macos
139 | ```
140 |
141 |
142 | ## Making a Pull Request
143 |
144 | Once your submission is ready, you should make a pull request on GitHub to the appropriate branch.
145 |
146 | * If you are implementing a new feature or bug-fix for the next release, then you should `base` your pull request on `master`.
147 |
148 | * If you are making a bug-fix for an old release, you should `base` on the appropriate `release/version` branch.
149 |
150 | We review pull requests within 24 hours.
151 |
152 |
153 | ## Releases
154 |
155 | Buckaroo releases are semantically versioned Git tags. You can see [the releases on GitHub](https://github.com/LoopPerfect/buckaroo/releases).
156 |
157 | Each release has a corresponding branch named `release/version`. This allows `master` to progress beyond the current release, whilst still allowing for easy patching of old versions.
158 |
159 |
160 | ### Installing a Cutting-edge Release ✋⚠️
161 |
162 | If you would like to use the latest version of Buckaroo from `master`, then you can do this using [Homebrew](https://brew.sh/) or [Linuxbrew](http://linuxbrew.sh/):
163 |
164 | ```bash=
165 | brew install --HEAD loopperfect/lp/buckaroo
166 | ```
167 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 LoopPerfect
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | # Buckaroo
6 |
7 | The decentralized package manager for C++ and friends.
8 |
9 | [](https://travis-ci.org/LoopPerfect/buckaroo) [](https://ci.appveyor.com/project/njlr/buckaroo)
10 | [](https://github.com/LoopPerfect/buckaroo/wiki)
11 |
12 | ## Why Buckaroo?
13 |
14 | Package managers like Yarn and Cargo have shown how productive developers can be when they can easily integrate a large ecosystem of projects. Buckaroo fills this gap for C++.
15 |
16 | The Buckaroo workflow looks like this:
17 |
18 | ```bash=
19 | # Create your project file
20 | $ buckaroo init
21 |
22 | # Install dependencies
23 | $ buckaroo add github.com/buckaroo-pm/boost-thread@branch=master
24 |
25 | # Run your code
26 | $ buck run :my-app
27 | ```
28 |
29 | We have an [FAQ](https://github.com/LoopPerfect/buckaroo/wiki/FAQ).
30 |
31 | ### Package Registries
32 |
33 | Pull dependencies directly from GitHub, BitBucket, GitLab, hosted Git and HTTP. [How?](https://github.com/LoopPerfect/buckaroo/wiki/Git-as-a-Package-Registry)
34 |
35 |
36 |
37 |
38 |
39 | ### IDE Integrations
40 |
41 | Support for major IDEs and tools. Integration guides can be found in [the docs](https://github.com/LoopPerfect/buckaroo/wiki/).
42 |
43 |
44 |
45 |
46 |
47 | ### Features
48 |
49 | C++ has unique requirements, so Buckaroo is a highly sophisticated piece of software.
50 |
51 | * Pull dependencies directly from GitHub, BitBucket, GitLab, hosted Git and HTTP
52 | * Fully reproducible builds and dependency resolution
53 | * Completely decentralized - there is no central server or publishing process
54 | * Allows any build configuration (even on a package-by-package basis)
55 | * Private and public dependencies to avoid "dependency hell"
56 | * Multiple libraries per package, so tools like Lerna are unnecessary
57 | * Pull individual packages out of mono-repos
58 | * Full support for semantic versioning (but only when you want it!)
59 | * Live at head! Move fast by depending directly on Git branches, but in a controlled way
60 | * Blazing fast resolution using clever heuristics
61 | * Version equivalency checks to reduce dependency conflicts
62 | * TOML configuration files for convenient editing by computers and humans
63 | * Works offline (with a populated cache)
64 | * Enable Upgrade Bot to keep everything up-to-date with a single click
65 |
66 | ### Get Started
67 |
68 | Please refer to [the Wiki](https://github.com/LoopPerfect/buckaroo/wiki) for [installation instructions](https://github.com/LoopPerfect/buckaroo/wiki/installation)! ✌️
69 |
70 | #### Quick Install
71 |
72 | Buckaroo is shipped as a self-contained executable, so all you need to do is download the bundle from the [releases page](https://github.com/LoopPerfect/buckaroo/releases).
73 |
74 | ##### Linux
75 |
76 | ```bash
77 | $ wget https://github.com/LoopPerfect/buckaroo/releases/download/v2.2.0/buckaroo-linux -O buckaroo
78 | $ chmod +x ./buckaroo
79 | $ ./buckaroo
80 | ```
81 |
82 | ##### macOS
83 |
84 | With Homebrew:
85 |
86 | ```bash
87 | $ brew install loopperfect/lp/buckaroo
88 | ```
89 |
90 | Or without Homebrew:
91 |
92 | ```bash
93 | $ wget https://github.com/LoopPerfect/buckaroo/releases/download/v2.2.0/buckaroo-macos -O buckaroo
94 | $ chmod +x ./buckaroo
95 | $ ./buckaroo
96 | ```
97 |
98 | ##### Windows
99 |
100 | You can use the [Chocolatey package](https://chocolatey.org/packages/buckaroo):
101 |
102 | ```bash
103 | choco install buckaroo
104 | ```
105 |
106 | Or, download [buckaroo.exe](https://github.com/LoopPerfect/buckaroo/releases/download/v2.0.3/buckaroo-windows.exe) from the [releases page](https://github.com/LoopPerfect/buckaroo/releases/v2.2.0).
107 |
108 | ### How Buckaroo Works
109 |
110 | The Buckaroo model is very simple. Packages live in source-control, and a manifest file is used to describe dependencies. This points to further manifests to create a dependency graph. Buckaroo works directly over Git and HTTP.
111 |
112 |
113 |
114 |
115 |
116 | Head over to [the Wiki](https://github.com/LoopPerfect/buckaroo/wiki) for more detailed information.
117 |
118 | ## Attribution
119 |
120 | SVG graphics in diagrams are made by [Freepik](http://www.freepik.com/) from [www.flaticon.com](https://www.flaticon.com/) and are licensed by [Creative Commons BY 3.0](http://creativecommons.org/licenses/by/3.0/).
121 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | version: '1.0.{build}'
2 | image: Visual Studio 2017
3 |
4 | before_build:
5 | - ps: dotnet --version
6 |
7 | build_script:
8 | - ps: dotnet build ./buckaroo
9 | - ps: dotnet build ./buckaroo-cli
10 | - ps: ./warp-bundle-windows.ps1
11 |
12 | clone_depth: 1
13 |
14 | test_script:
15 | - ps: dotnet test ./buckaroo-tests
16 |
17 | artifacts:
18 | - path: ./warp/buckaroo-windows.exe
19 | name: buckaroo-windows.exe
20 |
21 | deploy:
22 | description: 'AppVeyor Release'
23 | provider: GitHub
24 | auth_token:
25 | secure: Z+IaDd5u9nwMMBWqxrYd5l8igQcBV2EQij0wJA4jr1F78Nd6UrC9d4xuWLr4WI/n
26 | artifact: buckaroo-windows.exe
27 | draft: true
28 | prerelease: true
29 | on:
30 | appveyor_repo_tag: true
31 |
--------------------------------------------------------------------------------
/buckaroo-cli/.gitignore:
--------------------------------------------------------------------------------
1 | /bin
2 | /obj
3 |
--------------------------------------------------------------------------------
/buckaroo-cli/Program.fs:
--------------------------------------------------------------------------------
1 | open System
2 |
3 | []
4 | let main argv =
5 | async {
6 | let session = Guid.NewGuid () |> string
7 | let input = argv |> String.concat " "
8 |
9 | let! telemetry =
10 | Buckaroo.Telemetry.postCommand session input
11 | |> Async.Catch
12 | |> Async.Ignore
13 | |> Async.StartChild
14 |
15 | let! exitCode = async {
16 | try
17 | match Buckaroo.Command.parse input with
18 | | Ok (command, loggingLevel, fetchStyle) ->
19 | let! exitCode =
20 | command
21 | |> Buckaroo.Command.runCommand loggingLevel fetchStyle
22 | return exitCode
23 | | Error error ->
24 | Console.WriteLine error
25 | return 1
26 | with error ->
27 | Console.WriteLine error
28 | return 1
29 | }
30 |
31 | do! telemetry
32 | return exitCode
33 | }
34 | |> Async.RunSynchronously
35 |
--------------------------------------------------------------------------------
/buckaroo-cli/buckaroo-cli.fsproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net6.0
6 | false
7 | true
8 | false
9 | false
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/buckaroo-cli/nuget.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/buckaroo-tests/.gitignore:
--------------------------------------------------------------------------------
1 | /bin
2 | /obj
3 |
--------------------------------------------------------------------------------
/buckaroo-tests/Bash.fs:
--------------------------------------------------------------------------------
1 | module Buckaroo.Tests.Bash
2 |
3 | open Xunit
4 |
5 | open Buckaroo
6 | open System.Text
7 |
8 | #if OS_WINDOWS
9 |
10 | // No Bash on Windows
11 |
12 | #else
13 |
14 | // []
15 | // let ``Bash.runBash works correctly`` () =
16 | // let stdout = new StringBuilder()
17 | // let exitCode =
18 | // Bash.runBash "hello" (stdout.Append >> ignore) ignore
19 | // |> Async.RunSynchronously
20 | // Assert.Equal(0, exitCode)
21 | // Assert.Equal("Hello, world!", stdout.ToString())
22 |
23 | []
24 | let ``Bash.runBashSync works correctly`` () =
25 | let stdout = new StringBuilder()
26 | let exitCode =
27 | Bash.runBashSync "true" "" (stdout.Append >> ignore) ignore
28 | |> Async.RunSynchronously
29 | Assert.Equal(0, exitCode)
30 | Assert.Equal("", stdout.ToString().Trim())
31 |
32 | []
33 | let ``Stress test of Bash.runBashSync works correctly`` () =
34 | let task =
35 | Bash.runBashSync "true" "" ignore ignore
36 |
37 | let exitCodes =
38 | task
39 | |> List.replicate 128
40 | |> Seq.chunkBySize 16
41 | |> Seq.map Async.Parallel
42 | |> Seq.collect Async.RunSynchronously
43 | |> Seq.toList
44 |
45 | Assert.True(exitCodes |> Seq.exists (fun x -> x <> 0) |> not)
46 |
47 | #endif
48 |
--------------------------------------------------------------------------------
/buckaroo-tests/Command.fs:
--------------------------------------------------------------------------------
1 | module Buckaroo.Tests.Command
2 |
3 | open Xunit
4 | open Buckaroo
5 | open Buckaroo.Console
6 |
7 | let private defaultLoggingLevel = LoggingLevel.Info
8 |
9 | let private verboseLoggingLevel = LoggingLevel.Trace
10 |
11 | let private abcDef = Adhoc { Owner = "abc"; Project = "def" }
12 |
13 | let private ijkXyz = GitHub { Owner = "ijk"; Project = "xyz" }
14 |
15 | []
16 | let ``Command.parse works correctly`` () =
17 | let cases = [
18 | (Result.Ok (Command.Init, defaultLoggingLevel, RemoteFirst), "init");
19 |
20 | (Result.Ok (Command.Install, defaultLoggingLevel, RemoteFirst), " install ");
21 |
22 | (Result.Ok (Command.Resolve Quick, defaultLoggingLevel, RemoteFirst), "resolve");
23 | (Result.Ok (Command.Resolve Quick, verboseLoggingLevel, RemoteFirst), "resolve --verbose");
24 | (Result.Ok (Command.Resolve Upgrading, defaultLoggingLevel, RemoteFirst), "resolve --upgrade ");
25 | (Result.Ok (Command.Resolve Upgrading, verboseLoggingLevel, RemoteFirst), "resolve --upgrade --verbose");
26 | (Result.Ok (Command.Resolve Quick, defaultLoggingLevel, CacheFirst), "resolve --cache-first ");
27 | (Result.Ok (Command.Resolve Quick, verboseLoggingLevel, CacheFirst), "resolve --cache-first --verbose");
28 |
29 | (Result.Ok (Command.UpgradeDependencies [], defaultLoggingLevel, RemoteFirst), "upgrade");
30 | (Result.Ok (Command.UpgradeDependencies [ abcDef ], defaultLoggingLevel, RemoteFirst), "upgrade abc/def");
31 | (Result.Ok (Command.UpgradeDependencies [], verboseLoggingLevel, RemoteFirst), " upgrade --verbose ");
32 | (Result.Ok (Command.UpgradeDependencies [ abcDef ], verboseLoggingLevel, RemoteFirst), "upgrade abc/def --verbose ");
33 | (Result.Ok (Command.UpgradeDependencies [], verboseLoggingLevel, CacheFirst), " upgrade --cache-first --verbose ");
34 | (Result.Ok (Command.UpgradeDependencies [ abcDef ], verboseLoggingLevel, CacheFirst), "upgrade abc/def --cache-first --verbose ");
35 |
36 | (
37 | Result.Ok
38 | (
39 | Command.AddDependencies
40 | [ { Package = ijkXyz; Constraint = Constraint.wildcard; Targets = None } ],
41 | defaultLoggingLevel,
42 | RemoteFirst
43 | ),
44 | "add github.com/ijk/xyz "
45 | );
46 |
47 | (
48 | Result.Ok (Command.UpgradeDependencies [ abcDef; ijkXyz ], verboseLoggingLevel, RemoteFirst),
49 | "upgrade abc/def github.com/ijk/xyz --verbose "
50 | );
51 | ]
52 |
53 | for (expected, input) in cases do
54 | let actual = Command.parse input
55 |
56 | match actual with
57 | | Result.Error error ->
58 | System.Console.WriteLine (error + "\nfor \"" + input + "\"")
59 | | _ -> ()
60 |
61 | Assert.Equal(expected, actual)
62 |
--------------------------------------------------------------------------------
/buckaroo-tests/Constraint.fs:
--------------------------------------------------------------------------------
1 | module Buckaroo.Tests.Constraint
2 |
3 | open System
4 | open Xunit
5 |
6 | open Buckaroo
7 |
8 | let dropError<'T, 'E> (x : Result<'T, 'E>) =
9 | match x with
10 | | Result.Ok o -> Some o
11 | | Result.Error _ -> None
12 |
13 | []
14 | let ``Constraint.parse works correctly`` () =
15 | let cases = [
16 | ("*", Constraint.wildcard |> Some);
17 | ("revision=aabbccddee", Version.Git(GitVersion.Revision "aabbccddee") |> Exactly |> Some);
18 | ("!*", Constraint.wildcard |> Constraint.Complement |> Some);
19 | ("any(branch=master)", Some(Any <| Set [ Exactly (Version.Git(GitVersion.Branch "master"))]));
20 | ("any(any(branch=master))", Some(Any <| Set[ Any <| Set [ Exactly (Version.Git(GitVersion.Branch "master"))]]));
21 | ("any(revision=aabbccddee branch=master)", Some (Any <| Set[
22 | Exactly (Version.Git(GitVersion.Revision "aabbccddee"));
23 | Exactly (Version.Git(GitVersion.Branch "master"))]));
24 | ("all(*)", Some(All <| Set[Constraint.wildcard]));
25 | (
26 | "all(branch=master !revision=aabbccddee)",
27 | Some (All <| Set[Exactly (Version.Git(GitVersion.Branch "master")); Complement (Exactly (Version.Git(GitVersion.Revision "aabbccddee")))])
28 | );
29 | (
30 | "all(branch=master !any(revision=aabbccddee branch=develop))",
31 | Some (All <| Set[
32 | Exactly (Version.Git(GitVersion.Branch "master"));
33 | Complement (Any(Set[
34 | Exactly (Version.Git(GitVersion.Revision "aabbccddee"));
35 | Exactly (Version.Git(GitVersion.Branch "develop"));
36 | ]))
37 | ])
38 | );
39 | ("", None)
40 | ("1.0.0", Some (Exactly (Version.SemVer {
41 | SemVer.zero with Major = 1
42 | })));
43 | (
44 | "^1.0.0",
45 | Some (Constraint.rangeToConstraint RangeType.Major (SemVer.create (1, 0, 0, 0)))
46 | );
47 | (
48 | "~1.0.0",
49 | Some (Constraint.rangeToConstraint RangeType.Minor (SemVer.create (1, 0, 0, 0)))
50 | );
51 | (
52 | "+1.0.0",
53 | Some (Constraint.rangeToConstraint RangeType.Patch (SemVer.create (1, 0, 0, 0)))
54 | );
55 | ("all(branch=master ^1.0.0)", Some (All <| Set[
56 | Exactly (Git (GitVersion.Branch "master"));
57 | Constraint.rangeToConstraint RangeType.Major (SemVer.create (1, 0, 0, 0))
58 | ]
59 | ));
60 | ("all(^1.0.0 branch=master)", Some (All <| Set[
61 | Constraint.rangeToConstraint RangeType.Major (SemVer.create (1, 0, 0, 0));
62 | Exactly (Git (GitVersion.Branch "master"))
63 | ]))
64 | ]
65 |
66 | for (input, expected) in cases do
67 | Assert.Equal(expected, Constraint.parse input |> dropError)
68 |
69 | []
70 | let ``Constraint.satisfies works correctly`` () =
71 | let v = Version.Git(GitVersion.Revision "aabbccddee")
72 | let w = Version.Git(GitVersion.Tag "rc1")
73 | let c = Constraint.Exactly v
74 | Assert.True(Constraint.satisfies c (set [ v ]))
75 | Assert.False(Constraint.satisfies c (set [ w ]))
76 |
77 | []
78 | let ``Constraint.compare works correctly`` () =
79 | let input = [
80 | (Constraint.Exactly <| Version.Git(GitVersion.Branch "master"));
81 | (Constraint.Exactly <| Version.Git(GitVersion.Tag "v1.0.0"));
82 | (Constraint.wildcard);
83 | (Constraint.Exactly <| Version.Git(GitVersion.Revision "aabbccddee"));
84 | ]
85 | let expected = [
86 | (Constraint.Exactly <| Version.Git(GitVersion.Revision "aabbccddee"));
87 | (Constraint.Exactly <| Version.Git(GitVersion.Tag "v1.0.0"));
88 | (Constraint.Exactly <| Version.Git(GitVersion.Branch "master"));
89 | (Constraint.wildcard);
90 | ]
91 | let actual = input |> List.sortWith Constraint.compare
92 | Assert.Equal>(expected, actual)
93 |
94 | []
95 | let ``Constraint.simplify works correctly`` () =
96 | let cases = [
97 | ("!!revision=aabbccddee", "revision=aabbccddee");
98 | ("any(any(revision=aabbccddee))", "revision=aabbccddee");
99 | ("all(all(revision=aabbccddee))", "revision=aabbccddee");
100 | ("any(all(revision=aabbccddee))", "revision=aabbccddee");
101 | ("all(any(revision=aabbccddee))", "revision=aabbccddee");
102 | ("any(branch=master any(revision=aabbccddee))", "any(revision=aabbccddee branch=master)");
103 | ("any(all() revision=aabbccddee)", "any(revision=aabbccddee all())");
104 | ]
105 | for (input, expected) in cases do
106 | let actual =
107 | Constraint.parse input
108 | |> Result.map Constraint.simplify
109 | Assert.Equal(Constraint.parse expected, actual)
110 |
--------------------------------------------------------------------------------
/buckaroo-tests/Dependency.fs:
--------------------------------------------------------------------------------
1 | module Buckaroo.Tests.Dependency
2 |
3 | open System
4 | open Xunit
5 |
6 | open Buckaroo
7 |
8 | []
9 | let ``Dependency.parse works correctly`` () =
10 | let p = PackageIdentifier.GitHub { Owner = "abc"; Project = "def" }
11 | let cases = [
12 | ("github.com/abc/def@*", { Package = p; Constraint = Constraint.wildcard; Targets = None } |> Result.Ok)
13 | // TODO:
14 | // ("github.com/abc/def@*//:foo", { Package = p; Constraint = Constraint.wildcard; Targets = Some [ { Folders = []; Name = "foo" } ] } |> Result.Ok)
15 | // ("", Result.Error "");
16 | ]
17 | for (input, expected) in cases do
18 | Assert.Equal(expected, Dependency.parse input)
19 |
--------------------------------------------------------------------------------
/buckaroo-tests/Git.fs:
--------------------------------------------------------------------------------
1 | module Buckaroo.Tests.Git
2 |
3 | open Xunit
4 | open Buckaroo
5 |
6 | []
7 | let ``Git.parseBranchOrTag works correctly`` () =
8 | let cases = [
9 | (true, "abc");
10 | (true, "abc/def");
11 | (true, "abc-def");
12 | (false, "");
13 | (false, "abc..def");
14 | ]
15 |
16 | for (expected, input) in cases do
17 | Assert.Equal(expected, input |> Git.parseBranchOrTag |> Result.isOk)
18 |
--------------------------------------------------------------------------------
/buckaroo-tests/Glob.fs:
--------------------------------------------------------------------------------
1 | module Buckaroo.Tests.Glob
2 |
3 | open Xunit
4 | open Buckaroo
5 |
6 | []
7 | let ``Glob.isLike works correctly`` () =
8 | Assert.True("a.txt" |> Glob.isLike "**/*")
9 | Assert.True("a/b.txt" |> Glob.isLike "**/*")
10 | Assert.True("a/b/c.txt" |> Glob.isLike "**/*")
11 | Assert.True("boost-array-2618e0d5bbb70ddcd68daa285898be88ddbae714" |> Glob.isLike "*")
12 | Assert.True("boost-array-2618e0d5bbb70ddcd68daa285898be88ddbae714/" |> Glob.isLike "*")
13 |
--------------------------------------------------------------------------------
/buckaroo-tests/Lock.fs:
--------------------------------------------------------------------------------
1 | module Buckaroo.Tests.Lock
2 |
3 | open Xunit
4 | open FSharpx
5 | open Buckaroo
6 |
7 | []
8 | let ``Lock.parse works correctly 1`` () =
9 | let actual =
10 | [
11 | "manifest = \"aabbccddee\"";
12 | ]
13 | |> String.concat "\n"
14 | |> Lock.parse
15 |
16 | let expected = {
17 | ManifestHash = "aabbccddee";
18 | Dependencies = Set.empty;
19 | Packages = Map.empty;
20 | }
21 |
22 | // 3 new-lines indicates poor formatting
23 | Assert.True (
24 | expected
25 | |> Lock.toToml
26 | |> String.contains "\n\n\n"
27 | |> not
28 | )
29 |
30 | Assert.Equal(Result.Ok expected, actual)
31 |
32 | []
33 | let ``Lock.parse works correctly 2`` () =
34 | let actual =
35 | [
36 | "manifest = \"aabbccddee\"";
37 | "";
38 | "[[dependency]]";
39 | "package = \"abc/def\"";
40 | "target = \"//:def\"";
41 | "";
42 | "[lock.\"abc/def\"]";
43 | "url = \"https://www.abc.com/def.zip\"";
44 | "versions = [\"1.2.3\"]";
45 | "sha256 = \"aabbccddee\"";
46 | ]
47 | |> String.concat "\n"
48 | |> Lock.parse
49 |
50 | let expected = {
51 | ManifestHash = "aabbccddee"
52 | Dependencies =
53 | [
54 | {
55 | PackagePath = [], PackageIdentifier.Adhoc { Owner = "abc"; Project = "def" }
56 | Target = {
57 | Folders = []
58 | Name = "def"
59 | }
60 | }
61 | ]
62 | |> Set.ofSeq;
63 | Packages =
64 | Map.empty
65 | |> Map.add
66 | (PackageIdentifier.Adhoc { Owner = "abc"; Project = "def" })
67 | {
68 | Versions = Set [Version.SemVer { SemVer.zero with Major = 1; Minor = 2; Patch = 3 }];
69 | Location =
70 | (
71 | Buckaroo.PackageLock.Http
72 | (
73 | {
74 | Url = "https://www.abc.com/def.zip";
75 | StripPrefix = None;
76 | Type = None;
77 | },
78 | "aabbccddee"
79 | )
80 | );
81 | PrivatePackages = Map.empty;
82 | };
83 | }
84 |
85 | // 3 new-lines indicates poor formatting
86 | Assert.True (
87 | expected
88 | |> Lock.toToml
89 | |> String.contains "\n\n\n"
90 | |> not
91 | )
92 |
93 | Assert.Equal(Result.Ok expected, actual)
94 |
95 | []
96 | let ``Lock.parse works correctly 3`` () =
97 | let actual =
98 | [
99 | "manifest = \"aabbccddee\"";
100 | "";
101 | "[[dependency]]";
102 | "package = \"abc/def\"";
103 | "target = \"//:def\"";
104 | "";
105 | "[lock.\"abc/def\"]";
106 | "url = \"https://www.abc.com/def.zip\"";
107 | "versions = [\"1.2.3\"]";
108 | "sha256 = \"aabbccddee\"";
109 | "";
110 | "[lock.\"abc/def\".lock.\"ijk/xyz\"]";
111 | "url = \"https://www.ijk.com/xyz.zip\"";
112 | "versions = [\"1\"]";
113 | "sha256 = \"aabbccddee\"";
114 | "";
115 | ]
116 | |> String.concat "\n"
117 | |> Lock.parse
118 |
119 | let expected = {
120 | ManifestHash = "aabbccddee"
121 | Dependencies =
122 | [
123 | {
124 | PackagePath = [], PackageIdentifier.Adhoc { Owner = "abc"; Project = "def" }
125 | Target = {
126 | Folders = []
127 | Name = "def"
128 | }
129 | }
130 | ]
131 | |> Set.ofSeq;
132 | Packages =
133 | Map.empty
134 | |> Map.add
135 | (PackageIdentifier.Adhoc { Owner = "abc"; Project = "def" })
136 | {
137 | Versions = Set [Version.SemVer { SemVer.zero with Major = 1; Minor = 2; Patch = 3 }];
138 | Location =
139 | (
140 | PackageLock.Http
141 | (
142 | ({
143 | Url = "https://www.abc.com/def.zip";
144 | StripPrefix = None;
145 | Type = None;
146 | }),
147 | "aabbccddee"
148 | )
149 | );
150 | PrivatePackages =
151 | Map.empty
152 | |> Map.add
153 | (PackageIdentifier.Adhoc { Owner = "ijk"; Project = "xyz" })
154 | {
155 | Versions = Set.singleton (Version.SemVer { SemVer.zero with Major = 1; });
156 | Location =
157 | (PackageLock.Http (
158 | ({
159 | Url = "https://www.ijk.com/xyz.zip";
160 | StripPrefix = None;
161 | Type = None;
162 | }),
163 | "aabbccddee"
164 | ));
165 | PrivatePackages = Map.empty;
166 | };
167 | };
168 | }
169 |
170 | // 3 new-lines indicates poor formatting
171 | Assert.True (
172 | expected
173 | |> Lock.toToml
174 | |> String.contains "\n\n\n"
175 | |> not
176 | )
177 |
178 | Assert.Equal(Result.Ok expected, actual)
179 |
--------------------------------------------------------------------------------
/buckaroo-tests/Manifest.fs:
--------------------------------------------------------------------------------
1 | module Buckaroo.Tests.Manifest
2 |
3 | open Xunit
4 | open FSharpx
5 |
6 | open Buckaroo
7 | open Buckaroo.Tests
8 |
9 | []
10 | let ``Manifest.parse works correctly`` () =
11 | let a = {
12 | Package = PackageIdentifier.GitHub { Owner = "abc"; Project = "def" };
13 | Constraint = Constraint.wildcard;
14 | Targets = None
15 | }
16 |
17 | let b = {
18 | Package = PackageIdentifier.GitHub { Owner = "ijk"; Project = "xyz" };
19 | Constraint = Constraint.wildcard;
20 | Targets = Some [ { Folders = []; Name = "foo" } ]
21 | }
22 |
23 | let lmnqrs = { Owner = "lmn"; Project = "qrs" }
24 |
25 | let c = {
26 | Package = PackageIdentifier.Adhoc lmnqrs;
27 | Constraint = Constraint.wildcard;
28 | Targets = None;
29 | }
30 |
31 | let locationC =
32 | PackageSource.Http (
33 | Map.ofSeq [
34 | (
35 | Version.SemVer { SemVer.zero with Major = 1 }, {
36 | Url = "https://lmn/qrs.zip";
37 | StripPrefix = Some "%";
38 | Type = None;
39 | }
40 | )
41 | ]
42 | )
43 |
44 | let cases =
45 | [
46 | ("", Result.Ok Manifest.zero);
47 |
48 | (
49 | "[[dependency]]\npackage = \"github.com/abc/def\"\nversion = \"*\"",
50 | Result.Ok { Manifest.zero with Dependencies = set [ a ] }
51 | );
52 |
53 | (
54 | "[[dependency]]\npackage = \"github.com/ijk/xyz\"\nversion = \"*\"\ntargets = [ \"//:foo\" ]",
55 | Result.Ok { Manifest.zero with Dependencies = set [ b ] });
56 |
57 | (
58 | "[[dependency]]\npackage = \"lmn/qrs\"\nversion = \"*\"\n\n" +
59 | "[[location]]\npackage = \"lmn/qrs\"\nversion = \"1.0.0\"\nurl = \"https://lmn/qrs.zip\"\nstrip_prefix = \"%\"",
60 | Result.Ok
61 | {
62 | Manifest.zero with
63 | Dependencies = set [ c ];
64 | Locations = Map.ofSeq [
65 | (lmnqrs, locationC)
66 | ]
67 | }
68 | );
69 | ]
70 |
71 | for (input, expected) in cases do
72 | let actual = Manifest.parse input
73 |
74 | Assert.Equal(expected, actual)
75 |
76 |
77 | []
78 | let ``Manifest.toToml roundtrip 1`` () =
79 | let expected : Manifest = {
80 | Manifest.zero with
81 | Targets = Set [
82 | {Folders=["foo"; "bar"]; Name = "xxx"}
83 | {Folders=["foo"; "bar"]; Name = "yyy"}
84 | ]
85 | Dependencies = Set [{
86 | Targets = Some ([{Folders=["foo"; "bar"]; Name = "xxx"}])
87 | Constraint = All <| Set[Constraint.Exactly (Version.SemVer SemVer.zero)]
88 | Package = PackageIdentifier.GitHub { Owner = "abc"; Project = "def" }
89 | }]
90 | }
91 |
92 | let actual = expected |> Manifest.toToml |> Manifest.parse
93 |
94 | // 3 new-lines indicates poor formatting
95 | Assert.True (
96 | expected
97 | |> Manifest.toToml
98 | |> String.contains "\n\n\n"
99 | |> not
100 | )
101 |
102 | Assert.Equal (Result.Ok expected, actual)
103 |
104 | []
105 | let ``Manifest.toToml roundtrip 2`` () =
106 | let expected : Manifest = {
107 | Targets =
108 | Set [
109 | { Folders = [ "foo"; "bar"]; Name = "xxx" }
110 | { Folders = [ "foo"; "bar"]; Name = "yyy" }
111 | ]
112 | Tags = Set [ "c++"; "java"; "ml" ]
113 | Locations = Map.ofSeq [
114 | ({Owner = "testorg1"; Project = "test1"}, PackageSource.Http (Map.ofSeq [
115 | (Version.SemVer { SemVer.zero with Major = 1 }, {
116 | Url = "https://test.com"
117 | StripPrefix = Some "prefix"
118 | Type = Some ArchiveType.Zip
119 | })
120 | ]));
121 | ({Owner = "testorg2"; Project = "test2"}, PackageSource.Http (Map.ofSeq [
122 | (Version.SemVer { SemVer.zero with Major = 2 }, {
123 | Url = "https://testing.com"
124 | StripPrefix = Some "other_prefix"
125 | Type = Some ArchiveType.Zip
126 | })
127 | ]))
128 | ]
129 | Dependencies = Set [{
130 | Targets = Some ([{Folders=["foo"; "bar"]; Name = "xxx"}])
131 | Constraint = All <| Set[Constraint.Exactly (Version.SemVer SemVer.zero)]
132 | Package = PackageIdentifier.GitHub { Owner = "abc"; Project = "def" }
133 | }]
134 | PrivateDependencies = Set [{
135 | Targets = Some ([{Folders=["foo"; "bar"]; Name = "yyy"}])
136 | Constraint = Any <|Set[Constraint.Exactly (Version.SemVer SemVer.zero)]
137 | Package = PackageIdentifier.GitHub { Owner = "abc"; Project = "def" }
138 | }]
139 | Overrides = Map.empty
140 | }
141 |
142 | let actual = expected |> Manifest.toToml |> Manifest.parse
143 |
144 | // 3 new-lines indicates poor formatting
145 | Assert.True (
146 | expected
147 | |> Manifest.toToml
148 | |> String.contains "\n\n\n"
149 | |> not
150 | )
151 |
152 | Assert.Equal (Result.Ok expected, actual)
153 |
154 | []
155 | let ``Manifest.toToml roundtrip 3`` () =
156 | let expected : Manifest = {
157 | Manifest.zero with
158 | Overrides =
159 | Map.empty
160 | |> Map.add
161 | (PackageIdentifier.GitHub { Owner = "abc"; Project = "def" })
162 | (PackageIdentifier.GitHub { Owner = "abc"; Project = "pqr" })
163 | |> Map.add
164 | (PackageIdentifier.GitHub { Owner = "ijk"; Project = "lmo" })
165 | (PackageIdentifier.GitHub { Owner = "gfh"; Project = "xyz" })
166 | }
167 |
168 | let actual = expected |> Manifest.toToml |> Manifest.parse
169 |
170 | // 3 new-lines indicates poor formatting
171 | Assert.True (
172 | expected
173 | |> Manifest.toToml
174 | |> String.contains "\n\n\n"
175 | |> not
176 | )
177 |
178 | Assert.Equal (Result.Ok expected, actual)
179 |
--------------------------------------------------------------------------------
/buckaroo-tests/PackageIdentifier.fs:
--------------------------------------------------------------------------------
1 | module Buckaroo.Tests.PackageIdentifier
2 |
3 | open Xunit
4 | open Buckaroo
5 |
6 | []
7 | let ``PackageIdentifier.parse works correctly`` () =
8 | let cases = [
9 | ("github.com/abc/def", PackageIdentifier.GitHub { Owner = "abc"; Project = "def" });
10 | ("github.com/abc/def.ghi", PackageIdentifier.GitHub { Owner = "abc"; Project = "def.ghi" });
11 | ("github+abc/def", PackageIdentifier.GitHub { Owner = "abc"; Project = "def" });
12 | ("github+abc/def_ghi", PackageIdentifier.GitHub { Owner = "abc"; Project = "def_ghi" });
13 | ("bitbucket.org/abc/def", PackageIdentifier.BitBucket { Owner = "abc"; Project = "def" });
14 | ("gitlab.com/abc/def", PackageIdentifier.GitLab { Groups = [ "abc" ]; Project = "def" });
15 | ("gitlab.com/abc-def/xyz", PackageIdentifier.GitLab { Groups = [ "abc-def" ]; Project = "xyz" });
16 | ("github.com/ABC-DEF/XYZ", PackageIdentifier.GitHub { Owner = "abc-def"; Project = "xyz" });
17 | ("gitlab.com/ABC-DEF/XYZ", PackageIdentifier.GitLab { Groups = [ "abc-def" ]; Project = "xyz" });
18 | ("bitbucket.org/ABC-DEF/XYZ", PackageIdentifier.BitBucket { Owner = "abc-def"; Project = "xyz" });
19 | ("gitlab.com/abc/def/xyz", PackageIdentifier.GitLab { Groups = [ "abc"; "def" ]; Project = "xyz" });
20 | ]
21 |
22 | for (input, expected) in cases do
23 | // Parses to what we expect?
24 | Assert.Equal(Result.Ok expected, PackageIdentifier.parse input)
25 |
26 | // Round-trip via show
27 | Assert.Equal(Result.Ok expected, expected |> PackageIdentifier.show |> PackageIdentifier.parse)
28 |
--------------------------------------------------------------------------------
/buckaroo-tests/Paths.fs:
--------------------------------------------------------------------------------
1 | module Buckaroo.Tests.Paths
2 |
3 | open System
4 | open System.IO
5 | open Xunit
6 | open Buckaroo
7 |
8 | let private sep = String [| Path.DirectorySeparatorChar |]
9 |
10 | []
11 | let ``Paths.normalize works correctly`` () =
12 | let cases = [
13 | (".", "");
14 | ("a", "a");
15 | ("a" + sep + "", "a" + sep + "");
16 | ("" + sep + "a" + sep + "", "" + sep + "a" + sep + "");
17 | ("a" + sep + " " + sep + "b", "a" + sep + " " + sep + "b");
18 | ("" + sep + "a" + sep + "", "" + sep + "a" + sep + "");
19 | ("b", "a" + sep + ".." + sep + "b");
20 | ("c" + sep + "d", "a" + sep + ".." + sep + "b" + sep + ".." + sep + "c" + sep + "." + sep + "." + sep + "." + sep + "d");
21 | (".." + sep + ".." + sep + ".." + sep + "a" + sep + "b" + sep + "c", ".." + sep + ".." + sep + ".." + sep + "a" + sep + "b" + sep + "c");
22 | (".." + sep + ".." + sep + ".." + sep + "a", ".." + sep + ".." + sep + ".." + sep + "." + sep + "a");
23 | (
24 | ".." + sep + ".." + sep + ".." + sep + ".." + sep + ".." + sep + ".." + sep + "buckaroo" + sep + "github" + sep + "buckaroo-pm" + sep + "pkg-config-cairo",
25 | ".." + sep + ".." + sep + ".." + sep + ".." + sep + ".." + sep + ".." + sep + "." + sep + "buckaroo" + sep + "github" + sep + "buckaroo-pm" + sep + "pkg-config-cairo"
26 | );
27 | ]
28 |
29 | for (expected, input) in cases do
30 | Assert.Equal(expected, Paths.normalize input)
31 |
--------------------------------------------------------------------------------
/buckaroo-tests/Program.fs:
--------------------------------------------------------------------------------
1 | open System
2 |
3 | []
4 | let main argv = 0
--------------------------------------------------------------------------------
/buckaroo-tests/SemVer.fs:
--------------------------------------------------------------------------------
1 | module Buckaroo.Tests.SemVer
2 |
3 | open System
4 | open Xunit
5 |
6 | open Buckaroo
7 |
8 | let dropError<'T, 'E> (x : Result<'T, 'E>) =
9 | match x with
10 | | Result.Ok o -> Some o
11 | | Result.Error _ -> None
12 |
13 | []
14 | let ``SemVer.compare works correctly`` () =
15 | Assert.True(SemVer.compare SemVer.zero SemVer.zero = 0)
16 | Assert.True(SemVer.compare { SemVer.zero with Major = 1 } SemVer.zero = 1)
17 | Assert.True(SemVer.compare { SemVer.zero with Major = 1; Minor = 2 } { SemVer.zero with Major = 1 } = 1)
18 |
19 | []
20 | let ``SemVer.parse works correctly`` () =
21 | let cases = [
22 | ("7", Some { SemVer.zero with Major = 7 });
23 | ("6.4", Some { SemVer.zero with Major = 6; Minor = 4 });
24 | ("1.2.3", Some { SemVer.zero with Major = 1; Minor = 2; Patch = 3 });
25 | (" 1.2.3 ", Some { SemVer.zero with Major = 1; Minor = 2; Patch = 3 });
26 | (" 4.5.6.78", Some { Major = 4; Minor = 5; Patch = 6; Increment = 78 });
27 | ("v1.2.3", Some { SemVer.zero with Major = 1; Minor = 2; Patch = 3 });
28 | ("V4.2.7", Some { SemVer.zero with Major = 4; Minor = 2; Patch = 7 });
29 | ("", None);
30 | ("abc", None);
31 | ("v0.9.0-g++-4.9", None);
32 | ("boost-1.66.0", Some { SemVer.zero with Major = 1; Minor = 66 });
33 | ("boost-1.64.0-beta2", None);
34 | ]
35 | for (input, expected) in cases do
36 | Assert.Equal(expected, SemVer.parse input |> dropError)
37 |
--------------------------------------------------------------------------------
/buckaroo-tests/Target.fs:
--------------------------------------------------------------------------------
1 | module Buckaroo.Tests.Target
2 |
3 | open System
4 | open Xunit
5 |
6 | open Buckaroo
7 |
8 | []
9 | let ``Target.parse works correctly`` () =
10 | let input = "//path/to/some:target"
11 | let expected = Result.Ok { Folders = [ "path"; "to"; "some" ]; Name = "target" }
12 | Assert.Equal(expected, Target.parse input)
13 |
14 | let input = "//:foo"
15 | let expected = Result.Ok { Folders = []; Name = "foo" }
16 | Assert.Equal(expected, Target.parse input)
17 |
18 | let input = "//foo"
19 | let expected = Result.Ok { Folders = [ "foo" ]; Name = "foo" }
20 | Assert.Equal(expected, Target.parse input)
21 |
22 | let input = "//foo/bar"
23 | let expected = Result.Ok { Folders = [ "foo"; "bar" ]; Name = "bar" }
24 | Assert.Equal(expected, Target.parse input)
25 |
26 | let input = "//abc/def"
27 | let expected = Result.Ok { Folders = [ "abc"; "def" ]; Name = "def" }
28 | Assert.Equal(expected, Target.parse input)
29 |
30 | let input = ":bar"
31 | let expected = Result.Ok { Folders = []; Name = "bar" }
32 | Assert.Equal(expected, Target.parse input)
33 |
34 | let input = "foo:bar"
35 | let expected = Result.Ok { Folders = [ "foo" ]; Name = "bar" }
36 | Assert.Equal(expected, Target.parse input)
37 |
38 | let input = "//foo_bar:bar_bar"
39 | let expected = Result.Ok { Folders = [ "foo_bar" ]; Name = "bar_bar" }
40 | Assert.Equal(expected, Target.parse input)
41 |
42 | let input = "//Common++:Common++"
43 | let expected = Result.Ok { Folders = [ "Common++" ]; Name = "Common++" }
44 | Assert.Equal(expected, Target.parse input)
45 |
46 | let input = "//src/liblzma/:lzma"
47 | let expected = Result.Ok { Folders = [ "src"; "liblzma" ]; Name = "lzma" }
48 | Assert.Equal(expected, Target.parse input)
49 |
50 | []
51 | let ``Target.show works correctly`` () =
52 | let cases =
53 | [
54 | "//path/to/some:target"
55 | "//:foo"
56 | ]
57 |
58 | for case in cases do
59 | let actual =
60 | case
61 | |> Target.parse
62 | |> Result.map Target.show
63 |
64 | Assert.Equal(Result.Ok case, actual)
65 |
--------------------------------------------------------------------------------
/buckaroo-tests/Version.fs:
--------------------------------------------------------------------------------
1 | module Buckaroo.Tests.Version
2 |
3 | open System
4 | open Xunit
5 |
6 | open Buckaroo
7 |
8 | []
9 | let ``Version.parse works correctly`` () =
10 | let cases = [
11 | ("tag=abc", Version.Git(GitVersion.Tag "abc") |> Result.Ok);
12 | ("tag=foo/bar", Version.Git(GitVersion.Tag "foo/bar") |> Result.Ok);
13 | ("tag=v3.0.0", Version.Git(GitVersion.Tag "v3.0.0") |> Result.Ok);
14 | ("branch=master", Version.Git(GitVersion.Branch "master") |> Result.Ok);
15 | ("revision=aabbccddee", Version.Git(GitVersion.Revision "aabbccddee") |> Result.Ok);
16 | ("1.2", Version.SemVer { SemVer.zero with Major = 1; Minor = 2 } |> Result.Ok);
17 | ]
18 | for (input, expected) in cases do
19 | Assert.Equal(expected, Version.parse input)
20 |
21 | []
22 | let ``Version.compare works correctly`` () =
23 | Assert.Equal(-1, Version.compare (Version.Git(GitVersion.Branch "master")) (Version.Git(GitVersion.Branch "develop")))
24 | Assert.Equal(1, Version.compare (Version.Git(GitVersion.Branch "develop")) (Version.Git(GitVersion.Branch "master")))
25 | Assert.Equal(0, Version.compare (Version.Git(GitVersion.Branch "master")) (Version.Git(GitVersion.Branch "master")))
26 |
27 | let input = [
28 | (Version.Git(GitVersion.Branch "master"));
29 | (Version.Git(GitVersion.Tag "v1.0.0"));
30 | (Version.SemVer SemVer.zero);
31 | (Version.Git(GitVersion.Tag "v1.0.1"));
32 | (Version.SemVer {SemVer.zero with Patch = 1});
33 | (Version.Git(GitVersion.Branch "develop"));
34 | (Version.Git(GitVersion.Revision "aabbccddee"));
35 | ]
36 |
37 | let expected = [
38 | (Version.Git(GitVersion.Revision "aabbccddee"));
39 | (Version.Git(GitVersion.Tag "v1.0.0"));
40 | (Version.Git(GitVersion.Tag "v1.0.1"));
41 | (Version.SemVer SemVer.zero);
42 | (Version.SemVer {SemVer.zero with Patch = 1});
43 | (Version.Git(GitVersion.Branch "master"));
44 | (Version.Git(GitVersion.Branch "develop"));
45 | ]
46 |
47 | let actual = input |> List.sortWith Version.compare
48 |
49 | Assert.Equal>(expected, actual)
50 |
--------------------------------------------------------------------------------
/buckaroo-tests/buckaroo-tests.fsproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net6.0
4 |
5 |
6 | OS_WINDOWS
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/buckaroo.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.26124.0
5 | MinimumVisualStudioVersion = 15.0.26124.0
6 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "buckaroo", "buckaroo\buckaroo.fsproj", "{78DDAC61-2AAF-4D71-8A70-7D97F13BCC2D}"
7 | EndProject
8 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "buckaroo-cli", "buckaroo-cli\buckaroo-cli.fsproj", "{79BDFC97-89BB-4230-BDC8-EF30693F99EF}"
9 | EndProject
10 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "buckaroo-tests", "buckaroo-tests\buckaroo-tests.fsproj", "{9B776E2D-33B7-4825-A908-6287A5BBDE2C}"
11 | EndProject
12 | Global
13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
14 | Debug|Any CPU = Debug|Any CPU
15 | Debug|x64 = Debug|x64
16 | Debug|x86 = Debug|x86
17 | Release|Any CPU = Release|Any CPU
18 | Release|x64 = Release|x64
19 | Release|x86 = Release|x86
20 | EndGlobalSection
21 | GlobalSection(SolutionProperties) = preSolution
22 | HideSolutionNode = FALSE
23 | EndGlobalSection
24 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
25 | {78DDAC61-2AAF-4D71-8A70-7D97F13BCC2D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
26 | {78DDAC61-2AAF-4D71-8A70-7D97F13BCC2D}.Debug|Any CPU.Build.0 = Debug|Any CPU
27 | {78DDAC61-2AAF-4D71-8A70-7D97F13BCC2D}.Debug|x64.ActiveCfg = Debug|Any CPU
28 | {78DDAC61-2AAF-4D71-8A70-7D97F13BCC2D}.Debug|x64.Build.0 = Debug|Any CPU
29 | {78DDAC61-2AAF-4D71-8A70-7D97F13BCC2D}.Debug|x86.ActiveCfg = Debug|Any CPU
30 | {78DDAC61-2AAF-4D71-8A70-7D97F13BCC2D}.Debug|x86.Build.0 = Debug|Any CPU
31 | {78DDAC61-2AAF-4D71-8A70-7D97F13BCC2D}.Release|Any CPU.ActiveCfg = Release|Any CPU
32 | {78DDAC61-2AAF-4D71-8A70-7D97F13BCC2D}.Release|Any CPU.Build.0 = Release|Any CPU
33 | {78DDAC61-2AAF-4D71-8A70-7D97F13BCC2D}.Release|x64.ActiveCfg = Release|Any CPU
34 | {78DDAC61-2AAF-4D71-8A70-7D97F13BCC2D}.Release|x64.Build.0 = Release|Any CPU
35 | {78DDAC61-2AAF-4D71-8A70-7D97F13BCC2D}.Release|x86.ActiveCfg = Release|Any CPU
36 | {78DDAC61-2AAF-4D71-8A70-7D97F13BCC2D}.Release|x86.Build.0 = Release|Any CPU
37 | {79BDFC97-89BB-4230-BDC8-EF30693F99EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
38 | {79BDFC97-89BB-4230-BDC8-EF30693F99EF}.Debug|Any CPU.Build.0 = Debug|Any CPU
39 | {79BDFC97-89BB-4230-BDC8-EF30693F99EF}.Debug|x64.ActiveCfg = Debug|Any CPU
40 | {79BDFC97-89BB-4230-BDC8-EF30693F99EF}.Debug|x64.Build.0 = Debug|Any CPU
41 | {79BDFC97-89BB-4230-BDC8-EF30693F99EF}.Debug|x86.ActiveCfg = Debug|Any CPU
42 | {79BDFC97-89BB-4230-BDC8-EF30693F99EF}.Debug|x86.Build.0 = Debug|Any CPU
43 | {79BDFC97-89BB-4230-BDC8-EF30693F99EF}.Release|Any CPU.ActiveCfg = Release|Any CPU
44 | {79BDFC97-89BB-4230-BDC8-EF30693F99EF}.Release|Any CPU.Build.0 = Release|Any CPU
45 | {79BDFC97-89BB-4230-BDC8-EF30693F99EF}.Release|x64.ActiveCfg = Release|Any CPU
46 | {79BDFC97-89BB-4230-BDC8-EF30693F99EF}.Release|x64.Build.0 = Release|Any CPU
47 | {79BDFC97-89BB-4230-BDC8-EF30693F99EF}.Release|x86.ActiveCfg = Release|Any CPU
48 | {79BDFC97-89BB-4230-BDC8-EF30693F99EF}.Release|x86.Build.0 = Release|Any CPU
49 | {9B776E2D-33B7-4825-A908-6287A5BBDE2C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
50 | {9B776E2D-33B7-4825-A908-6287A5BBDE2C}.Debug|Any CPU.Build.0 = Debug|Any CPU
51 | {9B776E2D-33B7-4825-A908-6287A5BBDE2C}.Debug|x64.ActiveCfg = Debug|Any CPU
52 | {9B776E2D-33B7-4825-A908-6287A5BBDE2C}.Debug|x64.Build.0 = Debug|Any CPU
53 | {9B776E2D-33B7-4825-A908-6287A5BBDE2C}.Debug|x86.ActiveCfg = Debug|Any CPU
54 | {9B776E2D-33B7-4825-A908-6287A5BBDE2C}.Debug|x86.Build.0 = Debug|Any CPU
55 | {9B776E2D-33B7-4825-A908-6287A5BBDE2C}.Release|Any CPU.ActiveCfg = Release|Any CPU
56 | {9B776E2D-33B7-4825-A908-6287A5BBDE2C}.Release|Any CPU.Build.0 = Release|Any CPU
57 | {9B776E2D-33B7-4825-A908-6287A5BBDE2C}.Release|x64.ActiveCfg = Release|Any CPU
58 | {9B776E2D-33B7-4825-A908-6287A5BBDE2C}.Release|x64.Build.0 = Release|Any CPU
59 | {9B776E2D-33B7-4825-A908-6287A5BBDE2C}.Release|x86.ActiveCfg = Release|Any CPU
60 | {9B776E2D-33B7-4825-A908-6287A5BBDE2C}.Release|x86.Build.0 = Release|Any CPU
61 | EndGlobalSection
62 | EndGlobal
63 |
--------------------------------------------------------------------------------
/buckaroo/.gitignore:
--------------------------------------------------------------------------------
1 | /bin
2 | /obj
3 | /buckaroo
4 |
--------------------------------------------------------------------------------
/buckaroo/AddCommand.fs:
--------------------------------------------------------------------------------
1 | module Buckaroo.AddCommand
2 |
3 | open System.IO
4 | open Buckaroo.RichOutput
5 | open Buckaroo.Logger
6 |
7 | let task (context : Tasks.TaskContext) dependencies = async {
8 | let logger = createLogger context.Console None
9 |
10 | logger.RichInfo (
11 | (text "Adding dependency on ") +
12 | (
13 | dependencies
14 | |> Seq.map Dependency.showRich
15 | |> RichOutput.concat (text " ")
16 | )
17 | )
18 |
19 | let! manifest = Tasks.readManifest "."
20 |
21 | let newManifest = {
22 | manifest with
23 | Dependencies =
24 | manifest.Dependencies
25 | |> Seq.append dependencies
26 | |> Set.ofSeq;
27 | }
28 |
29 | if manifest = newManifest
30 | then
31 | logger.Warning ("The dependency already exists in the manifest. ")
32 | return 0
33 | else
34 | let! maybeLock = async {
35 | if File.Exists Constants.LockFileName
36 | then
37 | let! lock = Tasks.readLock
38 | return Some lock
39 | else
40 | return None
41 | }
42 |
43 | let! resolution =
44 | Solver.solve context Solution.empty newManifest ResolutionStyle.Quick maybeLock
45 |
46 | match resolution with
47 | | Result.Ok solution ->
48 | do! Tasks.writeManifest newManifest
49 | do! Tasks.writeLock (Lock.fromManifestAndSolution newManifest solution)
50 |
51 | let! install = InstallCommand.task context
52 |
53 | if install <> 0
54 | then
55 | logger.Error ("Failed to install the new dependency. ")
56 | return 1
57 | else
58 | logger.Success ("The dependency was added to the manifest and installed. ")
59 | return 0
60 | | _ ->
61 | logger.Error ("Failed to add the dependency. ")
62 | return 1
63 | }
64 |
--------------------------------------------------------------------------------
/buckaroo/Archive.fs:
--------------------------------------------------------------------------------
1 | module Buckaroo.Archive
2 |
3 | open System
4 | open System.IO
5 | open SharpCompress
6 | open SharpCompress.Common
7 | open SharpCompress.Archives
8 |
9 | let private extractRoot (archive : IArchive) (pattern : string) =
10 | let candidates =
11 | archive.Entries
12 | |> Seq.filter (fun e -> e.IsDirectory)
13 | |> Seq.map (fun e -> e.Key)
14 | |> Seq.filter (Glob.isLike pattern)
15 | |> Seq.distinct
16 | |> Seq.truncate 2
17 | |> Seq.toList
18 |
19 | match candidates with
20 | | head::[] -> head
21 | | [] ->
22 | raise <| Exception ("No directories matched the root")
23 | | xs ->
24 | raise <| Exception ("Multiple directories match the root: " + (string xs))
25 |
26 | let extractTo (pathToArchive : string) (pathToExtraction : string) (stripPrefix : string option) = async {
27 | use archive = Archives.Zip.ZipArchive.Open(pathToArchive) :> IArchive
28 |
29 | let extractionOptions = ExtractionOptions ()
30 | extractionOptions.ExtractFullPath <- false
31 | extractionOptions.Overwrite <- true
32 |
33 | let root =
34 | stripPrefix
35 | |> Option.map (extractRoot archive)
36 | |> Option.defaultValue ""
37 |
38 | let directoriesToExtract =
39 | archive.Entries
40 | |> Seq.filter (fun e -> e.IsDirectory)
41 | |> Seq.map (fun e -> e.Key)
42 | |> Seq.filter (fun e -> e.StartsWith root)
43 | |> Seq.distinct
44 |
45 | let entriesToExtract =
46 | archive.Entries
47 | |> Seq.filter (fun e -> not e.IsDirectory)
48 | |> Seq.filter (fun e -> e.Key.StartsWith root)
49 |
50 | for directory in directoriesToExtract do
51 | let target = Path.Combine(pathToExtraction, directory.Substring(root.Length))
52 | do! Files.mkdirp target
53 |
54 | for entry in entriesToExtract do
55 | let subPath = entry.Key.Substring(root.Length)
56 | let target = Path.Combine(pathToExtraction, subPath)
57 | do entry.WriteToFile(target, extractionOptions)
58 | }
59 |
--------------------------------------------------------------------------------
/buckaroo/ArchiveType.fs:
--------------------------------------------------------------------------------
1 | namespace Buckaroo
2 |
3 | type ArchiveType =
4 | | Zip
5 |
6 | module ArchiveType =
7 |
8 | type ParseError =
9 | | InvalidType of string
10 |
11 | module ParseError =
12 | let show (x : ParseError) =
13 | match x with
14 | | ParseError.InvalidType s -> s
15 |
16 | let parse (x : string) =
17 | match x.Trim().ToLower() with
18 | | "zip" -> Ok ArchiveType.Zip
19 | | _ -> x |> ParseError.InvalidType |> Error
20 |
21 | let show x =
22 | match x with
23 | | Zip -> "zip"
24 |
--------------------------------------------------------------------------------
/buckaroo/Atom.fs:
--------------------------------------------------------------------------------
1 | namespace Buckaroo
2 |
3 | type Atom =
4 | {
5 | Package : PackageIdentifier;
6 | Versions : Set
7 | }
8 | override this.ToString () =
9 | PackageIdentifier.show this.Package + "@" + Version.show this.Versions.MinimumElement
10 |
--------------------------------------------------------------------------------
/buckaroo/Bash.fs:
--------------------------------------------------------------------------------
1 | module Buckaroo.Bash
2 |
3 | open System
4 | open System.Diagnostics
5 | open System.Threading.Tasks
6 |
7 | type ProgressCallback = string -> Unit
8 |
9 | type BashException(command, exitCode) =
10 | inherit Exception("The command \"" + command + "\" exited with code " + (string exitCode))
11 | member this.Command = command
12 | member this.ExitCode = exitCode
13 |
14 | let escapeBash (command : string) =
15 | if command.Contains("\"") || command.Contains("$")
16 | then
17 | raise <| new Exception("Malicious bash? " + command)
18 | command
19 |
20 | let runBashSync (exe : String) (args : String) (stdoutHandler : ProgressCallback) (stderrHandler : ProgressCallback) = async {
21 | let startInfo = ProcessStartInfo()
22 |
23 | startInfo.CreateNoWindow <- true
24 | startInfo.UseShellExecute <- false
25 | startInfo.FileName <- exe
26 | startInfo.Arguments <- args
27 | startInfo.RedirectStandardOutput <- true
28 | startInfo.RedirectStandardError <- true
29 | startInfo.RedirectStandardInput <- true
30 | startInfo.WindowStyle <- ProcessWindowStyle.Hidden
31 |
32 | use p = new Process()
33 |
34 | p.StartInfo <- startInfo
35 |
36 | let startProcess () =
37 | p.OutputDataReceived.AddHandler(new DataReceivedEventHandler(fun _ event ->
38 | if event.Data <> null
39 | then
40 | stdoutHandler (event.Data + System.Environment.NewLine)
41 | ))
42 |
43 | p.ErrorDataReceived.AddHandler(new DataReceivedEventHandler(fun _ event ->
44 | if event.Data <> null
45 | then
46 | stderrHandler (event.Data + System.Environment.NewLine)
47 | ))
48 |
49 | p.Start() |> ignore
50 |
51 | p.BeginOutputReadLine()
52 | p.BeginErrorReadLine()
53 |
54 | p.WaitForExit()
55 |
56 | p.CancelOutputRead()
57 | p.CancelErrorRead()
58 |
59 | let! exitSignal =
60 | Task.Factory.StartNew(startProcess)
61 | |> Async.AwaitTask
62 | |> Async.StartChild
63 |
64 | do! exitSignal
65 |
66 | if p.ExitCode > 0
67 | then
68 | return
69 | raise <| new BashException(exe + " " + args, p.ExitCode)
70 |
71 | return p.ExitCode
72 | }
73 |
74 | let runBash (command : string) (stdoutHandler : ProgressCallback) (stderrHandler : ProgressCallback) = async {
75 | let startInfo = new ProcessStartInfo()
76 |
77 | startInfo.CreateNoWindow <- true
78 | startInfo.UseShellExecute <- false
79 | startInfo.FileName <- "/bin/bash"
80 | startInfo.Arguments <- "-c \"" + (escapeBash command) + "\""
81 | startInfo.RedirectStandardOutput <- true
82 | startInfo.RedirectStandardError <- true
83 | startInfo.RedirectStandardInput <- true
84 | startInfo.WindowStyle <- ProcessWindowStyle.Hidden
85 |
86 | use p = new Process()
87 |
88 | p.StartInfo <- startInfo
89 | p.EnableRaisingEvents <- true
90 |
91 | p.OutputDataReceived.AddHandler(new DataReceivedEventHandler(fun _ event ->
92 | if event.Data <> null
93 | then
94 | stdoutHandler event.Data
95 | ))
96 |
97 | p.ErrorDataReceived.AddHandler(new DataReceivedEventHandler(fun _ event ->
98 | if event.Data <> null
99 | then
100 | stderrHandler event.Data
101 | ))
102 |
103 | p.Start() |> ignore
104 |
105 | p.BeginOutputReadLine()
106 | p.BeginErrorReadLine()
107 |
108 | let! task =
109 | p.Exited
110 | |> Async.AwaitEvent
111 | |> Async.Ignore
112 | |> Async.StartChild
113 |
114 | do! task
115 |
116 | p.CancelOutputRead()
117 | p.CancelErrorRead()
118 |
119 | return p.ExitCode
120 | }
121 |
--------------------------------------------------------------------------------
/buckaroo/BitBucketApi.fs:
--------------------------------------------------------------------------------
1 | module Buckaroo.BitBucketApi
2 |
3 | open System
4 | open FSharp.Data
5 |
6 | let fetchFile (package : AdhocPackageIdentifier) (commit : Revision) (file : string) = async {
7 | if commit.Length <> 40
8 | then
9 | return raise <| new ArgumentException("BitBucket API requires full length commit hashes")
10 | else
11 | let url =
12 | "https://bitbucket.org/" + package.Owner + "/" + package.Project + "/raw/" + commit + "/" + file
13 | return! Http.AsyncRequestString(url)
14 | }
15 |
--------------------------------------------------------------------------------
/buckaroo/BuckConfig.fs:
--------------------------------------------------------------------------------
1 | module Buckaroo.BuckConfig
2 |
3 | type INIKey = string
4 |
5 | type INIValue =
6 | | INIString of string
7 | | INITuple of INIValue list
8 | | INIList of INIValue list
9 | | INIEmpty
10 |
11 | type INIData = Map>
12 |
13 | let rec renderValue (value : INIValue) : string =
14 | match value with
15 | | INIString s -> s
16 | | INITuple xs -> xs |> Seq.map renderValue |> String.concat ", "
17 | | INIList xs -> xs |> Seq.map renderValue |> String.concat ", "
18 | | INIEmpty -> ""
19 |
20 | let renderSection (section : Map) : string =
21 | section
22 | |> Seq.map (fun kvp -> " " + kvp.Key + " = " + renderValue kvp.Value)
23 | |> String.concat "\n"
24 |
25 | let render (config : INIData) : string =
26 | config
27 | |> Seq.map (fun kvp -> "[" + kvp.Key + "]" + "\n" + (renderSection kvp.Value))
28 | |> String.concat "\n\n"
29 |
--------------------------------------------------------------------------------
/buckaroo/BuildSystem.fs:
--------------------------------------------------------------------------------
1 | namespace Buckaroo
2 |
3 | type BuildSystem =
4 | | Bazel
5 | | Buck
6 |
--------------------------------------------------------------------------------
/buckaroo/Command.fs:
--------------------------------------------------------------------------------
1 | namespace Buckaroo
2 |
3 | open FSharp.Control
4 | open FSharpx
5 | open Buckaroo.Console
6 | open Buckaroo.Tasks
7 | open Buckaroo.Logger
8 |
9 | type Command =
10 | | Start
11 | | Help
12 | | Init
13 | | Version
14 | | Resolve of ResolutionStyle
15 | | Install
16 | | Quickstart
17 | | UpgradeDependencies of List
18 | | AddDependencies of List
19 | | RemoveDependencies of List
20 | | Explain of PackageIdentifier
21 | | ShowCompletions
22 |
23 | module Command =
24 |
25 | open System
26 | open System.IO
27 | open FParsec
28 | open Buckaroo.RichOutput
29 |
30 | let verboseParser : Parser = parse {
31 | let! maybeSkip =
32 | CharParsers.skipString "--verbose"
33 | |> Primitives.opt
34 | return Option.isSome maybeSkip
35 | }
36 |
37 | let cacheFirstParser : Parser = parse {
38 | let! cacheFirst =
39 | CharParsers.skipString "--cache-first"
40 | |> Primitives.opt
41 | return Option.isSome cacheFirst
42 | }
43 |
44 | let startParser : Parser = parse {
45 | do! CharParsers.spaces
46 | return Start
47 | }
48 |
49 | let initParser : Parser = parse {
50 | do! CharParsers.spaces
51 | do! CharParsers.skipString "init"
52 | do! CharParsers.spaces
53 | return Init
54 | }
55 |
56 | let helpParser : Parser = parse {
57 | do! CharParsers.spaces
58 | do! CharParsers.skipString "help"
59 | do! CharParsers.spaces
60 | return Help
61 | }
62 |
63 | let versionParser = parse {
64 | do! CharParsers.spaces
65 | do! CharParsers.skipString "version"
66 | do! CharParsers.spaces
67 | return Command.Version
68 | }
69 |
70 | let resolveParser = parse {
71 | do! CharParsers.spaces
72 | do! CharParsers.skipString "resolve"
73 | do! CharParsers.spaces
74 |
75 | let! strategy =
76 | parse {
77 | do! CharParsers.skipString "--upgrade"
78 |
79 | return ResolutionStyle.Upgrading
80 | }
81 | |> Primitives.opt
82 |
83 | return Resolve (strategy |> Option.defaultValue ResolutionStyle.Quick)
84 | }
85 |
86 | let installParser = parse {
87 | do! CharParsers.spaces
88 | do! CharParsers.skipString "install"
89 | do! CharParsers.spaces
90 | return Install
91 | }
92 |
93 | let quickstartParser = parse {
94 | do! CharParsers.spaces
95 | do! CharParsers.skipString "quickstart"
96 | do! CharParsers.spaces
97 | return Quickstart
98 | }
99 |
100 | let private packageToAddParser =
101 | parse {
102 | let! p = PackageIdentifier.parser
103 |
104 | return!
105 | parse {
106 | do! CharParsers.skipString "@"
107 | let! c = Constraint.parser
108 |
109 | return
110 | {
111 | Package = p;
112 | Constraint = c;
113 | Targets = None;
114 | }
115 | } <|>
116 | parse {
117 | return
118 | {
119 | Package = p;
120 | Constraint = Constraint.wildcard;
121 | Targets = None;
122 | }
123 | }
124 | }
125 |
126 | let addDependenciesParser = parse {
127 | do! CharParsers.spaces
128 | do! CharParsers.skipString "add"
129 | do! CharParsers.spaces1
130 |
131 | let! deps = Primitives.sepEndBy1 packageToAddParser CharParsers.spaces1
132 |
133 | return AddDependencies deps
134 | }
135 |
136 | let upgradeDepenenciesParser = parse {
137 | do! CharParsers.skipString "upgrade"
138 |
139 | let! packages =
140 | (attempt >> Primitives.many)
141 | <| parse {
142 | do! CharParsers.spaces1
143 | return! PackageIdentifier.parser
144 | }
145 |
146 | return UpgradeDependencies packages
147 | }
148 |
149 | let removeDependenciesParser = parse {
150 | do! CharParsers.spaces
151 | do! CharParsers.skipString "remove"
152 | do! CharParsers.spaces1
153 | let! deps = Primitives.sepBy PackageIdentifier.parser CharParsers.spaces1
154 | do! CharParsers.spaces
155 | return RemoveDependencies deps
156 | }
157 |
158 | let explainParser = parse {
159 | do! CharParsers.spaces
160 | do! CharParsers.skipString "explain"
161 | do! CharParsers.spaces1
162 | let! package = PackageIdentifier.parser
163 | do! CharParsers.spaces
164 | return Explain package
165 | }
166 |
167 | let showCompletionsParser : Parser = parse {
168 | do! CharParsers.spaces
169 | do! CharParsers.skipString "show-completions"
170 | do! CharParsers.spaces
171 | return ShowCompletions
172 | }
173 |
174 | let parser = parse {
175 | do! CharParsers.spaces
176 |
177 | let! command =
178 | resolveParser
179 | <|> upgradeDepenenciesParser
180 | <|> addDependenciesParser
181 | <|> removeDependenciesParser
182 | <|> installParser
183 | <|> quickstartParser
184 | <|> initParser
185 | <|> versionParser
186 | <|> helpParser
187 | <|> showCompletionsParser
188 | <|> explainParser
189 | <|> startParser
190 |
191 | do! CharParsers.spaces
192 |
193 | let! isCacheFirst = cacheFirstParser
194 | do! CharParsers.spaces
195 |
196 | let! isVerbose = verboseParser
197 | do! CharParsers.spaces
198 |
199 | let loggingLevel =
200 | if isVerbose
201 | then
202 | LoggingLevel.Trace
203 | else
204 | LoggingLevel.Info
205 |
206 | let fetchStyle =
207 | if isCacheFirst
208 | then
209 | CacheFirst
210 | else
211 | RemoteFirst
212 |
213 | return (command, loggingLevel, fetchStyle)
214 | }
215 |
216 | let parse (x : string) =
217 | match run (parser .>> CharParsers.eof) x with
218 | | Success(result, _, _) -> Result.Ok result
219 | | Failure(error, _, _) -> Result.Error error
220 |
221 | let add (context : Tasks.TaskContext) dependencies = async {
222 | let! manifest = Tasks.readManifest "."
223 | let newManifest = {
224 | manifest with
225 | Dependencies =
226 | manifest.Dependencies
227 | |> Seq.append dependencies
228 | |> Set.ofSeq;
229 | }
230 |
231 | if manifest = newManifest
232 | then
233 | return ()
234 | else
235 | let! maybeLock = async {
236 | if File.Exists (Constants.LockFileName)
237 | then
238 | let! lock = Tasks.readLock
239 | return Some lock
240 | else
241 | return None
242 | }
243 |
244 | let! resolution = Solver.solve context Solution.empty newManifest ResolutionStyle.Quick maybeLock
245 |
246 | match resolution with
247 | | Result.Ok solution ->
248 | do! Tasks.writeManifest newManifest
249 | do! Tasks.writeLock (Lock.fromManifestAndSolution newManifest solution)
250 | do! InstallCommand.task context |> Async.Ignore
251 |
252 | System.Console.WriteLine ("Success. ")
253 | | _ -> ()
254 | }
255 |
256 | let init context = async {
257 | let path = Constants.ManifestFileName
258 | let logger = createLogger context.Console None
259 |
260 | if File.Exists(path) |> not
261 | then
262 | use sw = File.CreateText(path)
263 |
264 | sw.Write (Manifest.zero |> Manifest.show)
265 | logger.Success ("Wrote " + Constants.ManifestFileName)
266 |
267 | return 0
268 | else
269 | logger.Warning "There is already a buckaroo.toml file in this directory. "
270 |
271 | return 1
272 | }
273 |
274 | let runCommand loggingLevel fetchStyle command = async {
275 | let! context = Tasks.getContext loggingLevel fetchStyle
276 |
277 | let! returnCode =
278 | match command with
279 | | Start -> StartCommand.task context
280 | | Init -> init context
281 | | Help -> HelpCommand.task context
282 | | Version -> VersionCommand.task context
283 | | Resolve style ->
284 | ResolveCommand.task context Solution.empty style
285 | |> Async.map (function | true -> 0 | false -> 1)
286 | | Install -> InstallCommand.task context
287 | | Quickstart -> QuickstartCommand.task context
288 | | UpgradeDependencies dependencies -> UpgradeCommand.task context dependencies
289 | | AddDependencies dependencies -> AddCommand.task context dependencies
290 | | RemoveDependencies dependencies -> RemoveCommand.task context dependencies
291 | | Explain package -> ExplainCommand.task context package
292 | | ShowCompletions -> ShowCompletions.task context
293 |
294 | do! context.Console.Flush ()
295 |
296 | return returnCode
297 | }
298 |
--------------------------------------------------------------------------------
/buckaroo/ConsoleManager.fs:
--------------------------------------------------------------------------------
1 | module Buckaroo.Console
2 |
3 | open System
4 | open Buckaroo.RichOutput
5 |
6 | type LoggingLevel =
7 | | Trace
8 | | Debug
9 | | Info
10 | | Silent
11 |
12 | type OutputCategory =
13 | | Normal
14 | | Warning
15 | | Error
16 |
17 | let private readPassword () =
18 | let rec loop password =
19 | let key = Console.ReadKey(true)
20 | if key.Key <> ConsoleKey.Backspace && key.Key <> ConsoleKey.Enter
21 | then
22 | Console.Write("*")
23 | loop (password + (string key.KeyChar))
24 | else
25 | if key.Key = ConsoleKey.Backspace && password.Length > 0
26 | then
27 | Console.Write("\b \b")
28 | loop (password.Substring(0, (password.Length - 1)))
29 | else
30 | if key.Key = ConsoleKey.Enter
31 | then
32 | password
33 | else
34 | loop password
35 | loop ""
36 |
37 | let private renderRichOutput (xs : RichOutput) =
38 | for x in xs.Segments do
39 | Console.ResetColor()
40 |
41 | match x.Foreground with
42 | | Some c ->
43 | Console.ForegroundColor <- c
44 | | _ -> ()
45 |
46 | match x.Background with
47 | | Some c ->
48 | Console.BackgroundColor <- c
49 | | _ -> ()
50 |
51 | Console.Write x.Text
52 |
53 | Console.Write(System.Environment.NewLine)
54 | Console.ResetColor()
55 |
56 | type ConsoleMessage =
57 | | Output of string * LoggingLevel * OutputCategory
58 | | RichOutput of RichOutput * LoggingLevel * OutputCategory
59 | | Input of AsyncReplyChannel
60 | | InputSecret of AsyncReplyChannel
61 | | Flush of AsyncReplyChannel
62 |
63 | type ConsoleManager (minimumLoggingLevel : LoggingLevel) =
64 |
65 | let actor = MailboxProcessor.Start(fun inbox -> async {
66 | while true do
67 | let! message = inbox.Receive()
68 | match message with
69 | | Output (m, l, c) ->
70 | if l >= minimumLoggingLevel
71 | then
72 | match c with
73 | | Normal -> Console.Out.WriteLine(m)
74 | | Warning -> Console.Out.WriteLine(m)
75 | | Error -> Console.Error.WriteLine(m)
76 | ()
77 | | RichOutput (m, l, c) ->
78 | if l >= minimumLoggingLevel
79 | then
80 | match c with
81 | | Normal -> renderRichOutput m
82 | | Warning -> renderRichOutput m
83 | | Error -> renderRichOutput m
84 | ()
85 | | Input channel ->
86 | let response = Console.ReadLine()
87 | channel.Reply(response)
88 | ()
89 | | InputSecret channel ->
90 | let secret = readPassword()
91 | channel.Reply(secret)
92 | ()
93 | | Flush channel ->
94 | do! Console.Out.FlushAsync() |> Async.AwaitTask
95 | do! Console.Error.FlushAsync() |> Async.AwaitTask
96 | channel.Reply()
97 | ()
98 | ()
99 | })
100 |
101 | member this.Write (message, loggingLevel) =
102 | actor.Post (Output (message, loggingLevel, OutputCategory.Normal))
103 |
104 | member this.Write (message : string) =
105 | this.Write(message, LoggingLevel.Info)
106 |
107 | member this.Write (message, loggingLevel) =
108 | actor.Post (RichOutput (message, loggingLevel, OutputCategory.Normal))
109 |
110 | member this.Write (message) =
111 | actor.Post (RichOutput (message, LoggingLevel.Info, OutputCategory.Normal))
112 |
113 | member this.Error (message, loggingLevel) =
114 | actor.Post (Output (message, loggingLevel, OutputCategory.Error))
115 |
116 | member this.Read () =
117 | actor.PostAndAsyncReply Input
118 |
119 | member this.ReadSecret () =
120 | actor.PostAndAsyncReply InputSecret
121 |
122 | member this.Flush () =
123 | actor.PostAndAsyncReply Flush
124 |
--------------------------------------------------------------------------------
/buckaroo/Constants.fs:
--------------------------------------------------------------------------------
1 | module Buckaroo.Constants
2 |
3 | []
4 | let Version = "3.0.0"
5 |
6 | []
7 | let PackagesDirectory = "buckaroo"
8 |
9 | []
10 | let ManifestFileName = "buckaroo.toml"
11 |
12 | []
13 | let LegacyManifestFileName = "buckaroo.json"
14 |
15 | []
16 | let LockFileName = "buckaroo.lock.toml"
17 |
18 | []
19 | let BuckarooMacrosFileName = "buckaroo_macros.bzl"
20 |
21 | []
22 | let BuckarooDefsFileName = "defs.bzl"
23 |
24 | []
25 | let BuckarooDepsFileName = "BUCKAROO_DEPS"
26 |
27 | []
28 | let MaxConsecutiveFailures = 10
29 |
--------------------------------------------------------------------------------
/buckaroo/Constraint.fs:
--------------------------------------------------------------------------------
1 | namespace Buckaroo
2 |
3 | type RangeType =
4 | | Major // ^1.2.3 in NPM
5 | | Minor // ~1.2.3 in NPM
6 | | Patch // Our own invention!
7 |
8 | type RangeComparatorTypes =
9 | | LTE
10 | | LT
11 | | GT
12 | | GTE
13 | with
14 | override this.ToString () =
15 | match this with
16 | | LTE -> "<="
17 | | LT -> "<"
18 | | GT -> ">"
19 | | GTE -> ">="
20 |
21 | type Constraint =
22 | | Exactly of Version
23 | | Range of RangeComparatorTypes * SemVer
24 | | Any of Set
25 | | All of Set
26 | | Complement of Constraint
27 |
28 | #nowarn "40"
29 |
30 | module Constraint =
31 |
32 | open FParsec
33 |
34 | let wildcard = All Set.empty
35 |
36 | let intersection (c : Constraint) (d : Constraint) : Constraint =
37 | All (Set[ c; d ])
38 |
39 | let union (c : Constraint) (d : Constraint) : Constraint =
40 | Any (Set[ c; d ])
41 |
42 | let complement (c : Constraint) : Constraint =
43 | Complement c
44 |
45 | let isWithinRange (c, v) (candidate : SemVer) =
46 | match c with
47 | | LTE ->
48 | candidate <= v
49 | | LT ->
50 | candidate < v
51 | | GT ->
52 | candidate > v
53 | | GTE ->
54 | candidate >= v
55 |
56 | let rec satisfies (c : Constraint) (vs : Set) : bool =
57 | match c with
58 | | Exactly u -> vs |> Set.toSeq |> Seq.exists(fun x -> x = u)
59 | | Complement x -> satisfies x vs |> not
60 | | Any xs -> xs |> Seq.exists (fun c -> satisfies c vs)
61 | | All xs -> xs |> Seq.forall (fun c -> satisfies c vs)
62 | | Range (op, v) ->
63 | vs
64 | |> Set.toSeq
65 | |> Seq.exists (fun x ->
66 | match x with
67 | | SemVer semVer -> semVer |> isWithinRange (op, v)
68 | | _ -> false
69 | )
70 |
71 | []
72 | let private MaxChanceOfSuccess = 1024
73 |
74 | let rec chanceOfSuccess (x : Constraint) : int =
75 | match x with
76 | | Exactly (Version.Git (Revision _)) -> 1
77 | | Exactly (Version.Git (Tag _)) -> 2
78 | | Exactly (Version.SemVer _) -> 3
79 | | Range _ -> 4
80 | | Exactly (Version.Git (Branch _)) -> 5
81 | | Any xs -> xs |> Seq.map chanceOfSuccess |> Seq.append [ 0 ] |> Seq.sum
82 | | All xs ->
83 | (xs |> Seq.map chanceOfSuccess |> Seq.append [ 0 ] |> Seq.max) -
84 | (xs |> Seq.map chanceOfSuccess |> Seq.append [ 0 ] |> Seq.sum)
85 | | Complement x -> MaxChanceOfSuccess - (chanceOfSuccess x)
86 |
87 | // TODO: Better Sorting!
88 | let rec compare (x : Constraint) (y : Constraint) : int =
89 | match (x, y) with
90 | | (Exactly u, Exactly v) -> Version.compare u v
91 | | (Range (_, u), Range (_, v)) ->
92 | Version.compare (Version.SemVer u) (Version.SemVer v)
93 | | (Range (_, v), Exactly b) ->
94 | Version.compare (Version.SemVer v) b
95 | | (Exactly a, Range (_, v)) ->
96 | Version.compare a (Version.SemVer v)
97 | | (Any xs, y) ->
98 | xs
99 | |> Seq.map (fun x -> compare x y)
100 | |> Seq.append [ -1 ]
101 | |> Seq.max
102 | | (y, Any xs) ->
103 | (compare (Any xs) y) * -1
104 | | (All xs, y) ->
105 | xs
106 | |> Seq.map (fun x -> compare x y)
107 | |> Seq.append [ 1 ]
108 | |> Seq.min
109 | | (y, All xs) ->
110 | (compare (All xs) y) * -1
111 | | (Complement c, y) ->
112 | compare y c
113 | | (y, Complement c) ->
114 | compare c y
115 |
116 | let rec show (c : Constraint) : string =
117 | match c with
118 | | Exactly v -> Version.show v
119 | | Complement c -> "!" + show c
120 | | Any xs ->
121 | "any(" +
122 | (xs
123 | |> Seq.map show
124 | |> String.concat " ") +
125 | ")"
126 | | All xs ->
127 | if Seq.isEmpty xs
128 | then "*"
129 | else
130 | "all(" +
131 | (xs
132 | |> Seq.map show
133 | |> String.concat " ") +
134 | ")"
135 | | Range (op, v) -> (string op) + (string v)
136 |
137 | let rec simplify (c : Constraint) : Constraint =
138 | let iterate c =
139 | match c with
140 | | Complement (Complement x) -> x
141 | | Constraint.All xs ->
142 | match xs |> Set.toList with
143 | | [ x ] -> x
144 | | xs ->
145 | xs
146 | |> Seq.collect (fun x ->
147 | match x with
148 | | All xs -> xs
149 | | _ -> Set[ x ]
150 | )
151 | |> Seq.sortDescending
152 | |> Seq.distinct
153 | |> Set
154 | |> Constraint.All
155 | | Constraint.Any xs ->
156 | match xs |> Set.toList with
157 | | [ x ] -> x
158 | | xs ->
159 | xs
160 | |> Seq.collect (fun x ->
161 | match x with
162 | | Any xs -> xs
163 | | _ -> Set[ x ]
164 | )
165 | |> Seq.sortDescending
166 | |> Seq.distinct
167 | |> Set
168 | |> Constraint.Any
169 | | _ -> c
170 |
171 | let next = iterate c
172 | if next = c
173 | then
174 | c
175 | else
176 | simplify next
177 |
178 | let wildcardParser = parse {
179 | do! CharParsers.skipString "*"
180 | return All Set.empty
181 | }
182 |
183 | let symbolParser<'T> (token : string, symbol : 'T) = parse {
184 | do! CharParsers.skipString token
185 | return symbol
186 | }
187 |
188 | let rangeTypeParser = choice [
189 | symbolParser ("^", Major)
190 | symbolParser ("~", Minor)
191 | symbolParser ("+", Patch)
192 | ]
193 |
194 | let rangeToConstraint rangeType semVer =
195 | let max =
196 | match rangeType with
197 | | Major ->
198 | { SemVer.zero with Major = semVer.Major + 1; }
199 | | Minor ->
200 | { SemVer.zero with Major = semVer.Major; Minor = semVer.Minor + 1 }
201 | | Patch ->
202 | { semVer with Patch = semVer.Patch + 1; Increment = 0 }
203 | Constraint.All
204 | (Set[
205 | Constraint.Range (GTE, semVer);
206 | Constraint.Range (LT, max);
207 | ])
208 |
209 | let rangeParser = parse {
210 | let! rangeType = rangeTypeParser
211 | let! semVer = SemVer.parser
212 |
213 | return rangeToConstraint rangeType semVer
214 | }
215 |
216 | let rangeComparatorParser = choice [
217 | symbolParser ("<=", LTE)
218 | symbolParser (">=", GTE)
219 | symbolParser ("<", LT)
220 | symbolParser (">", GT)
221 | ]
222 |
223 | let customRangeParser = parse {
224 | let! comparator = rangeComparatorParser
225 | let! semVer = SemVer.parser
226 |
227 | return Constraint.Range (comparator, semVer)
228 | }
229 |
230 | let exactlyParser = parse {
231 | let! version = Version.parser
232 | return Exactly version
233 | }
234 |
235 | let Parser = parse {
236 | let! version = Version.parser
237 | return Exactly version
238 | }
239 |
240 | let rec parser = parse {
241 | let complementParser = parse {
242 | do! CharParsers.skipString "!"
243 | let! c = parser
244 |
245 | return Complement c
246 | }
247 |
248 | let anyParser = parse {
249 | do! CharParsers.skipString "any("
250 | let! elements = CharParsers.spaces1 |> Primitives.sepBy parser
251 | do! CharParsers.skipString ")"
252 |
253 | return Any (Set elements)
254 | }
255 |
256 | let allParser = parse {
257 | do! CharParsers.skipString "all("
258 | let! elements = CharParsers.spaces1 |> Primitives.sepBy parser
259 | do! CharParsers.skipString ")"
260 |
261 | return All (Set elements)
262 | }
263 |
264 | return! choice [
265 | wildcardParser
266 | rangeParser
267 | attempt (customRangeParser)
268 | exactlyParser
269 | complementParser
270 | anyParser
271 | allParser
272 | ]
273 | }
274 |
275 | let parse (x : string) : Result =
276 | match run (parser .>> CharParsers.eof) x with
277 | | Success(result, _, _) -> Result.Ok result
278 | | Failure(error, _, _) -> Result.Error error
279 |
280 |
--------------------------------------------------------------------------------
/buckaroo/Dependency.fs:
--------------------------------------------------------------------------------
1 | namespace Buckaroo
2 |
3 | type Dependency =
4 | {
5 | Package : PackageIdentifier
6 | Constraint : Constraint
7 | Targets : Target list option
8 | }
9 |
10 | module Dependency =
11 |
12 | open FParsec
13 | open Buckaroo.RichOutput
14 |
15 | let satisfies (dependency : Dependency) (atom : Atom) =
16 | atom.Package = dependency.Package && atom.Versions |> Constraint.satisfies dependency.Constraint
17 |
18 | let show (x : Dependency) =
19 | (PackageIdentifier.show x.Package) + "@" + Constraint.show x.Constraint +
20 | (
21 | x.Targets
22 | |> Option.map (fun xs -> "[ " + (xs |> Seq.map Target.show |> String.concat " ") + " ]")
23 | |> Option.defaultValue ""
24 | )
25 |
26 | let showRich (x : Dependency) =
27 | (
28 | (
29 | PackageIdentifier.show x.Package
30 | |> text
31 | |> foreground System.ConsoleColor.Magenta
32 | ) +
33 | " at " +
34 | (
35 | Constraint.show x.Constraint
36 | |> text
37 | |> foreground System.ConsoleColor.Magenta
38 | )
39 | ) +
40 | (
41 | x.Targets
42 | |> Option.map (fun xs ->
43 | (RichOutput.text "[ ") +
44 | (xs
45 | |> Seq.map Target.show
46 | |> String.concat " "
47 | |> RichOutput.text
48 | |> RichOutput.foreground System.ConsoleColor.Green) +
49 | " ]"
50 | )
51 | |> Option.defaultValue (RichOutput.text "")
52 | )
53 |
54 | let parser = parse {
55 | let! p = PackageIdentifier.parser
56 | do! CharParsers.skipString "@"
57 | let! c = Constraint.parser
58 | return
59 | { Package = p; Constraint = c; Targets = None }
60 | }
61 |
62 | let parse (x : string) : Result =
63 | match run (parser .>> CharParsers.eof) x with
64 | | Success(result, _, _) -> Result.Ok result
65 | | Failure(errorMsg, _, _) -> Result.Error errorMsg
66 |
--------------------------------------------------------------------------------
/buckaroo/DownloadManager.fs:
--------------------------------------------------------------------------------
1 | namespace Buckaroo
2 |
3 | open System
4 | open System.IO
5 | open System.Text.RegularExpressions
6 | open FSharp.Data
7 | open FSharpx.Control
8 | open Buckaroo.RichOutput
9 | open Buckaroo.Console
10 | open Buckaroo.Hashing
11 |
12 | type CopyMessage =
13 | | Copy of string * string * AsyncReplyChannel>
14 |
15 | type DownloadMessage =
16 | | Download of string * AsyncReplyChannel>
17 |
18 | type DownloadManager (console : ConsoleManager, cacheDirectory : string) =
19 |
20 | let sanitizeFilename (x : string) =
21 | let regexSearch =
22 | new string (Path.GetInvalidFileNameChars()) +
23 | new string (Path.GetInvalidPathChars()) +
24 | "@.:\\/"
25 | let r = Regex (String.Format ("[{0}]", Regex.Escape regexSearch))
26 | Regex.Replace(r.Replace(x, "-"), "-{2,}", "-")
27 |
28 | let cachePath (url : string) =
29 | let hash = sha256 url
30 | Path.Combine(cacheDirectory, (sanitizeFilename url).ToLower() + "-" + hash.Substring(0, 16))
31 |
32 | let cachePathHash (hash : string) =
33 | Path.Combine(cacheDirectory, hash)
34 |
35 | let downloadFile (url : string) (target : string) = async {
36 | console.Write (
37 | (text "Downloading ") +
38 | (text url |> foreground ConsoleColor.Magenta) +
39 | " to " +
40 | (text target |> foreground ConsoleColor.Cyan) + "... ")
41 | let! request = Http.AsyncRequestStream url
42 | use outputFile = new FileStream(target, FileMode.Create)
43 | do!
44 | request.ResponseStream.CopyToAsync outputFile
45 | |> Async.AwaitTask
46 | return target
47 | }
48 |
49 | let hashCache = MailboxProcessor.Start (fun inbox -> async {
50 | let mutable cache = Map.empty
51 |
52 | while true do
53 | let! (Copy(source, destination, replyChannel)) = inbox.Receive()
54 | match cache |> Map.tryFind destination with
55 | | Some task -> replyChannel.Reply(task)
56 | | None ->
57 | let! task =
58 | async {
59 | if File.Exists destination |> not
60 | then
61 | do! Files.copy source destination
62 | }
63 | |> Async.StartChild
64 | cache <- cache |> Map.add destination task
65 | replyChannel.Reply(task)
66 | })
67 |
68 | let copy source destination = async {
69 | let! task = hashCache.PostAndAsyncReply (fun ch -> Copy (source, destination, ch))
70 |
71 | return! task
72 | }
73 |
74 | let downloadCache = MailboxProcessor.Start (fun inbox -> async {
75 | let mutable cache = Map.empty
76 |
77 | while true do
78 | let! (Download (url, replyChannel)) = inbox.Receive ()
79 |
80 | match cache |> Map.tryFind url with
81 | | Some task -> replyChannel.Reply(task)
82 | | None ->
83 | let target = cachePath url
84 | let! task =
85 | async {
86 | if File.Exists target
87 | then
88 | console.Write ((text "Deleting ") + (text target |> foreground ConsoleColor.Cyan) + "... ")
89 | do! Files.delete target
90 | let! cachePath = downloadFile url target
91 | let! hash = Files.sha256 cachePath
92 | let destination = cachePathHash hash
93 | do! copy cachePath destination
94 | return destination
95 | }
96 | |> Async.StartChild
97 |
98 | cache <- cache |> Map.add url task
99 | replyChannel.Reply(task)
100 | })
101 |
102 | member this.DownloadToCache (url : string) = async {
103 | let! res = downloadCache.PostAndAsyncReply(fun ch -> Download(url, ch))
104 | return! res
105 | }
106 |
107 | member this.Download (url : string) (path : string) = async {
108 | let! source = this.DownloadToCache url
109 | do! Files.copy source path
110 | }
111 |
112 | member this.DownloadHash (sha256 : string) (urls : string list) : Async =
113 | let rec processUrls urls = async {
114 | match urls with
115 | | head::tail ->
116 | let! cachePath = this.DownloadToCache head
117 | let! actualHash = Files.sha256 cachePath
118 |
119 | if actualHash = sha256
120 | then
121 | return cachePath
122 | else
123 | return! processUrls tail
124 | | [] ->
125 | return raise <| Exception "Ran out of URLs to try"
126 | }
127 | processUrls urls
128 |
--------------------------------------------------------------------------------
/buckaroo/ExplainCommand.fs:
--------------------------------------------------------------------------------
1 | module Buckaroo.ExplainCommand
2 |
3 | open FSharp.Control
4 | open Buckaroo.Tasks
5 | open Buckaroo.Logger
6 |
7 | let private explain (logger : Logger) (sourceExplorer : ISourceExplorer) (packageToExplain : PackageIdentifier) (lock : Lock) = async {
8 | let rec computeTraces traces = asyncSeq {
9 | for trace in traces do
10 | match trace with
11 | | head :: tail ->
12 | let package, isPrivate = head
13 |
14 | match lock.Packages |> Map.tryFind package with
15 | | Some lockedPackage ->
16 | yield head :: tail
17 |
18 | logger.Info ("Exploring " + (PackageIdentifier.show package) + "... ")
19 |
20 | let! manifest =
21 | sourceExplorer.FetchManifest (lockedPackage.Location, lockedPackage.Versions)
22 |
23 | let nextTraces =
24 | manifest.Dependencies
25 | |> Seq.map (fun dependency -> (dependency, false))
26 | |> Seq.append (manifest.PrivateDependencies |> Seq.map (fun dependency -> (dependency, true)))
27 | |> Seq.map (fun (dependency, isPrivate) -> (dependency.Package, isPrivate) :: head :: tail)
28 | |> Set.ofSeq
29 |
30 | yield! nextTraces |> AsyncSeq.ofSeq
31 |
32 | yield! computeTraces nextTraces
33 | | None -> ()
34 | | [] -> ()
35 | }
36 |
37 | let directDependencies =
38 | lock.Dependencies
39 | |> Seq.choose (fun target ->
40 | match target.PackagePath with
41 | | [], package -> Some [ (package, false) ]
42 | | _ -> None
43 | )
44 | |> Set.ofSeq
45 |
46 | let! traces =
47 | computeTraces directDependencies
48 | |> AsyncSeq.filter (fun trace ->
49 | match trace with
50 | | (head, _) :: _ -> head = packageToExplain
51 | | _ -> false
52 | )
53 | |> AsyncSeq.distinctUntilChanged
54 | |> AsyncSeq.toListAsync
55 |
56 | return
57 | traces
58 | |> Seq.sortBy List.length
59 | |> Seq.distinct
60 | |> Seq.toList
61 | }
62 |
63 | let task (context : TaskContext) (package : PackageIdentifier) = async {
64 | let logger = createLogger context.Console None
65 |
66 | logger.Info "Reading lock-file... "
67 |
68 | match! Tasks.readLockIfPresent with
69 | | Some lock ->
70 | logger.Info "Fetching traces... "
71 |
72 | let! traces = explain logger context.SourceExplorer package lock
73 |
74 | if Seq.isEmpty traces
75 | then
76 | logger.Success ("There are no traces for " + (PackageIdentifier.show package) + ". ")
77 | else
78 | logger.Success ("Found the following traces for " + (PackageIdentifier.show package) + ": ")
79 |
80 | for trace in traces do
81 | logger.Print
82 | <| " @ " +
83 | (
84 | trace
85 | |> List.rev
86 | |> Seq.map (fun (package, isPrivate) ->
87 | let arrow =
88 | if isPrivate
89 | then
90 | "--{private}--> "
91 | else
92 | "-----> "
93 | arrow + PackageIdentifier.show package
94 | )
95 | |> String.concat " "
96 | )
97 |
98 | return 0
99 | | None ->
100 | logger.Warning "No lock-file is present. Run buckaroo resolve first. "
101 | return 1
102 | }
103 |
--------------------------------------------------------------------------------
/buckaroo/Files.fs:
--------------------------------------------------------------------------------
1 | module Buckaroo.Files
2 |
3 | open System.IO
4 | open Buckaroo.Hashing
5 | open System
6 |
7 | []
8 | let private DefaultBufferSize = 4096
9 |
10 | let exists (path : string) = async {
11 | return File.Exists(path)
12 | }
13 |
14 | let copyFile (src : string) (dest : string) = async {
15 | return File.Copy (src, dest)
16 | }
17 |
18 | let directoryExists (path : string) = async {
19 | return Directory.Exists(path)
20 | }
21 |
22 | let delete (path : string) = async {
23 | return File.Delete path
24 | }
25 |
26 | let mkdirp (path : string) = async {
27 | if Directory.Exists(path) |> not
28 | then
29 | Directory.CreateDirectory(path) |> ignore
30 | return ()
31 | }
32 |
33 | let writeFile (path : string) (content : string) = async {
34 | use sw = new System.IO.StreamWriter(path)
35 | return! sw.WriteAsync(content) |> Async.AwaitTask
36 | }
37 |
38 | let touch (path : string) = async {
39 | if File.Exists path |> not
40 | then
41 | do! writeFile path ""
42 | }
43 |
44 | let readFile (path : string) = async {
45 | use sr = new StreamReader(path)
46 | return! sr.ReadToEndAsync() |> Async.AwaitTask
47 | }
48 |
49 | let copy (source : string) (destination : string) = async {
50 | use sourceFile = new FileStream(source, FileMode.Open, FileAccess.Read, FileShare.Read, DefaultBufferSize, true)
51 | use destFile = new FileStream(destination, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None, DefaultBufferSize, true)
52 | do!
53 | sourceFile.CopyToAsync(destFile)
54 | |> Async.AwaitTask
55 | }
56 |
57 | let sha256 (path : string) = async {
58 | let! content = readFile path
59 | return sha256 content
60 | }
61 |
62 | let deleteDirectoryIfExists (path : string) =
63 | async {
64 | try
65 | Directory.Delete(path, true)
66 | return true
67 | with
68 | | ex ->
69 | return
70 | if ex :? DirectoryNotFoundException
71 | then false
72 | else raise ex
73 | }
74 |
75 | let rec copyDirectory srcPath dstPath = async {
76 | if not <| System.IO.Directory.Exists(srcPath) then
77 | let msg = System.String.Format("Source directory does not exist or could not be found: {0}", srcPath)
78 | raise (System.IO.DirectoryNotFoundException(msg))
79 |
80 | if not <| System.IO.Directory.Exists(dstPath) then
81 | System.IO.Directory.CreateDirectory(dstPath) |> ignore
82 |
83 | let srcDir = new System.IO.DirectoryInfo(srcPath)
84 |
85 | for file in srcDir.GetFiles() do
86 | let temppath = System.IO.Path.Combine(dstPath, file.Name)
87 | file.CopyTo(temppath, true) |> ignore
88 |
89 | for subdir in srcDir.GetDirectories() do
90 | let dstSubDir = System.IO.Path.Combine(dstPath, subdir.Name)
91 | do! copyDirectory subdir.FullName dstSubDir
92 |
93 | ()
94 | }
95 |
--------------------------------------------------------------------------------
/buckaroo/Git.fs:
--------------------------------------------------------------------------------
1 | namespace Buckaroo
2 |
3 | open FSharp.Control
4 |
5 | type Branch = string
6 |
7 | type Revision = string
8 |
9 | type Tag = string
10 |
11 | type RefType =
12 | | Tag
13 | | Branch
14 |
15 | type Ref = {
16 | Revision : Revision
17 | Name : string
18 | Type: RefType
19 | }
20 |
21 | type IGit =
22 | abstract member Clone : string -> string -> Async
23 | abstract member DefaultBranch : string -> Async
24 | abstract member Unshallow : string -> Async
25 | abstract member UpdateRefs : string -> Async
26 | abstract member Checkout : string -> string -> Async
27 | abstract member ShallowClone : string -> string -> Async
28 | abstract member FetchBranch : string -> Branch -> int -> Async
29 | abstract member RemoteRefs : string -> Async[
30 | abstract member FetchCommits : string -> Branch -> AsyncSeq
31 | abstract member HasCommit : string -> Revision -> Async
32 | abstract member ReadFile : string -> Revision -> string -> Async
33 | abstract member CheckoutTo : string -> Revision -> string -> Async
34 |
35 | module Git =
36 |
37 | open FSharpx
38 | open FParsec
39 |
40 | // TODO: Complete impl of following rules:
41 | // They can include slash / for hierarchical (directory) grouping,
42 | // but no slash-separated component can begin with a dot . or end with the sequence .lock.
43 | // They must contain at least one /. This enforces the presence of a category like heads/, tags/ etc.
44 | // but the actual names are not restricted. If the --allow-onelevel option is used, this rule is waived.
45 | // They cannot have two consecutive dots .. anywhere.
46 | // They cannot have ASCII control characters (i.e. bytes whose values are lower than \040, or \177 DEL), space, tilde ~, caret ^, or colon : anywhere.
47 | // They cannot have question-mark ?, asterisk *, or open bracket [ anywhere. See the --refspec-pattern option below for an exception to this rule.
48 | // They cannot begin or end with a slash / or contain multiple consecutive slashes (see the --normalize option below for an exception to this rule)
49 | // They cannot end with a dot ..
50 | // They cannot contain a sequence @{.
51 | // They cannot be the single character @.
52 | // They cannot contain a \.
53 | let branchOrTagNameParser = parse {
54 | let! branchOrTag = CharParsers.regex @"[a-zA-Z0-9-/\.]{2,128}"
55 |
56 | let invalidSequences = [ ".."; "@{"; "\\"; "//" ]
57 |
58 | if invalidSequences |> Seq.exists branchOrTag.Contains
59 | then
60 | let errorMessage = "Cannot contain any of: " + (invalidSequences |> String.concat ", ")
61 | return! fail errorMessage
62 | else
63 | return branchOrTag
64 | }
65 |
66 | let parseBranchOrTag (x : string) : Result =
67 | match run branchOrTagNameParser x with
68 | | Success(result, _, _) -> Result.Ok result
69 | | Failure(errorMsg, _, _) -> Result.Error errorMsg
70 |
71 |
--------------------------------------------------------------------------------
/buckaroo/GitCli.fs:
--------------------------------------------------------------------------------
1 | namespace Buckaroo
2 |
3 | open System
4 | open System.Text
5 | open FSharp.Control
6 | open FSharpx
7 | open Buckaroo.Console
8 | open Buckaroo.RichOutput
9 | open Buckaroo.Bash
10 | open Buckaroo.Logger
11 |
12 | type GitCli (console : ConsoleManager) =
13 |
14 | let logger = createLogger console (Some "git")
15 |
16 | let nl = System.Environment.NewLine
17 |
18 | let runBash exe args = async {
19 | let rt =
20 | (
21 | "Running " + exe + " "
22 | |> RichOutput.text
23 | |> RichOutput.foreground ConsoleColor.Gray
24 | ) +
25 | (
26 | args
27 | |> RichOutput.text
28 | |> RichOutput.foreground ConsoleColor.White
29 | )
30 |
31 | console.Write (rt, LoggingLevel.Debug)
32 |
33 | let stdout = StringBuilder ()
34 |
35 | do!
36 | Bash.runBashSync exe args (stdout.Append >> ignore) ignore
37 | |> Async.Ignore
38 |
39 | return stdout.ToString()
40 | }
41 |
42 | let listLocalCommits repository branch skip = async {
43 | let args =
44 | "--no-pager -C " + repository +
45 | " log " + branch + " --pretty=format:'%H'" + " --skip=" + skip.ToString()
46 |
47 | let! output =
48 | runBash "git" args
49 | |> Async.Catch
50 | |> Async.map (fun x ->
51 | match x with
52 | | Choice1Of2 y -> y
53 | | Choice2Of2 _ -> ""
54 | )
55 |
56 | return
57 | output.Split ([| nl |], StringSplitOptions.RemoveEmptyEntries)
58 | |> Seq.map (fun x -> x.Trim())
59 | |> Seq.filter (fun x -> x.Length > 0)
60 | |> Seq.toList
61 | }
62 |
63 | member this.Init (directory : string) =
64 | runBash "git" ("init " + directory)
65 |
66 | member this.LocalTags (repository : String) = async {
67 | let gitDir = repository
68 | let command = "--no-pager -C " + gitDir + " tag"
69 |
70 | let! output = runBash "git" command
71 |
72 | return
73 | output.Split ([| nl |], StringSplitOptions.RemoveEmptyEntries)
74 | |> Seq.map (fun x -> x.Trim())
75 | |> Seq.toList
76 | }
77 |
78 | member this.LocalBranches (repository : String) = async {
79 | let gitDir = repository
80 | let command =
81 | "--no-pager -C " + gitDir +
82 | " branch"
83 | let! output = runBash "git" command
84 |
85 | return
86 | output.Split ([| nl |], StringSplitOptions.RemoveEmptyEntries)
87 | |> Seq.map (fun x -> x.Trim())
88 | |> Seq.filter (fun x -> x.Contains("*") |> not)
89 | |> Seq.toList
90 | }
91 |
92 | interface IGit with
93 | member this.Clone (url : string) (directory : string) = async {
94 | do!
95 | runBash "git" ("clone --bare " + url + " " + directory)
96 | |> Async.Ignore
97 | }
98 |
99 | member this.HasCommit (gitPath : string) (revision : Revision) = async {
100 | let command =
101 | "--no-pager -C " + gitPath +
102 | " log " + revision + " --pretty=format:'%H' -n0"
103 |
104 | let! result = runBash "git" command |> Async.Catch
105 |
106 | return
107 | match result with
108 | | Choice1Of2 _ -> true
109 | | Choice2Of2 _ -> false
110 | }
111 |
112 | member this.DefaultBranch (gitPath : string) = async {
113 | let! result = runBash "git" ("-C " + gitPath + " symbolic-ref HEAD")
114 |
115 | let parts = result.Split([| '/' |])
116 |
117 | return parts.[2].Trim()
118 | }
119 |
120 | member this.CheckoutTo (gitPath : string) (revision : Revision) (installPath : string) = async {
121 | do! Files.mkdirp installPath
122 | do!
123 | runBash "git" ("clone -s -n " + gitPath + " " + installPath)
124 | |> Async.Ignore
125 |
126 | try
127 | do!
128 | runBash "git" ("-C " + installPath + " checkout " + revision)
129 | |> Async.Ignore
130 | with _ ->
131 | do!
132 | runBash "git" ("-C " + installPath + " checkout --orphan " + revision)
133 | |> Async.Ignore
134 | }
135 |
136 | member this.Unshallow (gitDir : string) = async {
137 | do!
138 | runBash "git" ("-C " + gitDir + " fetch --unshallow")
139 | |> Async.Catch
140 | |> Async.Ignore
141 | do!
142 | runBash "git" ("-C " + gitDir + " fetch origin +refs/heads/*:refs/heads/* +refs/tags/*:refs/tags/*")
143 | |> Async.Ignore
144 | }
145 |
146 | member this.UpdateRefs (gitDir : string) = async {
147 | do!
148 | runBash "git" ("-C " + gitDir + " fetch origin +refs/heads/*:refs/heads/* +refs/tags/*:refs/tags/*")
149 | |> Async.Ignore
150 | }
151 |
152 | member this.Checkout (gitDir : string) (revision : string) = async {
153 | try
154 | do!
155 | runBash "git" ("-C " + gitDir + " checkout " + revision + " .")
156 | |> Async.Ignore
157 | with
158 | _ -> // If the commit is an orphan then this might work
159 | do!
160 | runBash "git" ("-C " + gitDir + " checkout --orphan " + revision + " ")
161 | |> Async.Ignore
162 | }
163 |
164 | member this.ShallowClone (url : String) (directory : string) = async {
165 | logger.RichInfo ((text "Shallow cloning ") + (highlight url))
166 | do!
167 | runBash "git" ("clone --bare --depth=1 " + url + " " + directory)
168 | |> Async.Ignore
169 | }
170 |
171 | member this.FetchBranch (repository : String) (branch : Branch) (depth : int) = async {
172 | let gitDir = repository
173 |
174 | let fetchToDepth depth = async {
175 | let depthStr =
176 | if depth > 0
177 | then "--depth=" + (string depth) + " "
178 | else ""
179 |
180 | let command =
181 | "--no-pager -C " + gitDir +
182 | " fetch origin " + depthStr + branch.Trim() + ":" + branch.Trim()
183 |
184 | do!
185 | runBash "git" command
186 | |> Async.Ignore
187 | }
188 |
189 | try
190 | do! fetchToDepth depth
191 | with
192 | | :? BashException as error ->
193 | if error.ExitCode > 0
194 | then
195 | // Delete the branch and try again
196 | // Seems like commits are cached (TODO: verify this)
197 | do!
198 | runBash "git" ("-C " + gitDir + " branch -D " + branch)
199 | |> Async.Ignore
200 |
201 | do! fetchToDepth depth
202 | }
203 |
204 | member this.FetchCommits (repository : String) (branch : Branch) : AsyncSeq = asyncSeq {
205 | yield!
206 | [0..12]
207 | |> Seq.map (fun i -> pown 2 i)
208 | |> Seq.map( fun depth skip -> async {
209 | let! revs =
210 | listLocalCommits repository branch skip
211 |
212 | let! fetchNext =
213 | (this :> IGit).FetchBranch repository branch <| (List.length revs) + depth
214 | |> Async.Ignore
215 | |> Async.StartChild
216 |
217 | return (revs, fetchNext)
218 | })
219 | |> AsyncSeq.ofSeq
220 | |> AsyncSeq.scanAsync
221 | (fun (skip, prev, _) next -> async {
222 | let! (nextList, fetchNext) = next skip
223 | return (skip + (nextList |> List.length), nextList, fetchNext)
224 | })
225 | ( 0, List.empty, async { return () } )
226 | |> AsyncSeq.takeWhile (fun (_, revs, _) -> revs.Length > 0)
227 | |> AsyncSeq.collect (fun (_, revs, fetchNext) -> asyncSeq {
228 | yield! revs |> AsyncSeq.ofSeq
229 | do! fetchNext
230 | })
231 | }
232 |
233 | member this.ReadFile (repository : String) (commit : Revision) (path : String) = async {
234 | let gitDir = repository
235 | let command =
236 | "-C " + gitDir +
237 | " show " + commit + ":" + path
238 | return! runBash "git" command
239 | }
240 |
241 | member this.RemoteRefs (url : String) = async {
242 | let cleanUpTag (tag : string) =
243 | if tag.EndsWith "^{}"
244 | then
245 | tag.Substring(0, tag.Length - 3)
246 | else
247 | tag
248 |
249 | let! output = runBash "git" ("--no-pager ls-remote --heads --tags " + url)
250 |
251 | return
252 | output.Split ([| nl |], StringSplitOptions.RemoveEmptyEntries)
253 | |> Seq.choose (fun x ->
254 | let parts = System.Text.RegularExpressions.Regex.Split(x, @"\s+")
255 | match parts with
256 | | [| commit; name |] ->
257 | let isTag = name.Contains("refs/tags/")
258 | match isTag with
259 | | true -> {
260 | Type = RefType.Tag;
261 | Revision = commit;
262 | Name =
263 | name.Trim().Substring("refs/tags/".Length)
264 | |> cleanUpTag
265 | }
266 | | false -> {
267 | Type = RefType.Branch;
268 | Revision = commit;
269 | Name = name.Trim().Substring("refs/heads/".Length)
270 | }
271 | |> Some
272 | | _ -> None
273 | )
274 | |> Seq.toList
275 | }
276 |
--------------------------------------------------------------------------------
/buckaroo/GitHubApi.fs:
--------------------------------------------------------------------------------
1 | module Buckaroo.GitHubApi
2 |
3 | open System
4 | open FSharp.Data
5 |
6 | let fetchFile (package : AdhocPackageIdentifier) (commit : Revision) (file : string) = async {
7 | if commit.Length <> 40
8 | then
9 | return raise <| new ArgumentException("GitHub API requires full length commit hashes")
10 | else
11 | let url =
12 | "https://raw.githubusercontent.com/" + package.Owner + "/" + package.Project + "/" + commit + "/" + file
13 | return! Http.AsyncRequestString(url)
14 | }
15 |
--------------------------------------------------------------------------------
/buckaroo/GitLabApi.fs:
--------------------------------------------------------------------------------
1 | module Buckaroo.GitLabApi
2 |
3 | open System
4 | open FSharp.Data
5 |
6 | let fetchFile (package : GitLabPackageIdentifier) (commit : Revision) (file : string) = async {
7 | if commit.Length <> 40
8 | then
9 | return raise <| ArgumentException("GitLab API requires full length commit hashes")
10 | else
11 | let url =
12 | "https://gitlab.com/" + (package.Groups |> String.concat "/") +
13 | "/" + package.Project + "/raw/" + commit + "/" + file
14 | return! Http.AsyncRequestString(url)
15 | }
16 |
--------------------------------------------------------------------------------
/buckaroo/GitLib.fs:
--------------------------------------------------------------------------------
1 | namespace Buckaroo
2 |
3 | open System
4 | open System.IO
5 | open LibGit2Sharp
6 | open LibGit2Sharp.Handlers
7 | open FSharpx
8 | open Buckaroo.Console
9 | open FSharp.Control
10 |
11 | type GitLib (console : ConsoleManager) =
12 |
13 | let rec requestUsername url = async {
14 | console.Write ("Please enter a username for " + url)
15 | let! username = console.Read()
16 | let trimmed = username.Trim()
17 | if trimmed |> String.length > 0
18 | then
19 | console.Write trimmed
20 | return trimmed
21 | else
22 | return! requestUsername url
23 | }
24 |
25 | let rec requestPassword username url = async {
26 | console.Write ("Please enter a password for " + username + "@" + url)
27 | let! password = console.ReadSecret()
28 | let trimmed = password.Trim()
29 | if trimmed |> String.length > 0
30 | then
31 | console.Write trimmed
32 | return trimmed
33 | else
34 | return! requestPassword username url
35 | }
36 |
37 | let credentialsHandler = new CredentialsHandler(fun url username _ ->
38 | (
39 | async {
40 | let! selectedUsername =
41 | if username <> null
42 | then async { return username }
43 | else requestUsername url
44 |
45 | let! password = requestPassword selectedUsername url
46 |
47 | let credentials = new UsernamePasswordCredentials()
48 |
49 | credentials.Username <- selectedUsername
50 | credentials.Password <- password
51 |
52 | return credentials :> Credentials
53 | }
54 | |> Async.RunSynchronously
55 | )
56 | )
57 |
58 | let createSharedGitConfig (path : string) =
59 | "[core]\n" +
60 | " repositoryformatversion = 0\n" +
61 | " filemode = true\n" +
62 | " bare = false\n" +
63 | " logallrefupdates = true\n" +
64 | "[remote \"origin\"]\n" +
65 | " url = " + path + "\n"+
66 | " fetch = +refs/heads/*:refs/remotes/origin/*\n"
67 |
68 | let sharedGitClone (src : string) (destination : string) = async {
69 | let gitPath = Path.Combine (destination, ".git")
70 | do! Files.mkdirp gitPath
71 | do! Files.mkdirp (Path.Combine (gitPath, "info"))
72 | do! Files.mkdirp (Path.Combine (gitPath, "hooks"))
73 | do! Files.mkdirp (Path.Combine (gitPath, "refs", "heads"))
74 | do! Files.mkdirp (Path.Combine (gitPath, "refs", "tags"))
75 | do! Files.mkdirp (Path.Combine (gitPath, "objects", "info"))
76 | do! Files.mkdirp (Path.Combine (gitPath, "objects", "pack"))
77 | do! Files.writeFile (Path.Combine (gitPath, "description")) ""
78 | do! Files.writeFile (Path.Combine (gitPath, "info", "exclude")) ""
79 | do!
80 | Files.copyFile
81 | (Path.Combine (src, "HEAD"))
82 | (Path.Combine (gitPath, "HEAD"))
83 |
84 | do!
85 | Files.writeFile
86 | (Path.Combine (gitPath, "config"))
87 | (createSharedGitConfig src)
88 |
89 | do!
90 | Files.writeFile
91 | (Path.Combine (gitPath, "objects", "info", "alternatives"))
92 | src
93 |
94 | return ()
95 | }
96 |
97 | let isTag (ref: LibGit2Sharp.Reference) =
98 | ref.CanonicalName.Contains("refs/tags/")
99 |
100 | let isHead (ref: LibGit2Sharp.Reference) =
101 | ref.CanonicalName.Contains("refs/heads/")
102 |
103 | member this.Init (directory : string) = async {
104 | Repository.Init (directory, true) |> ignore
105 | }
106 |
107 | member this.LocalTags (repository : string) = async {
108 | let repo = new Repository(repository)
109 | return repo.Tags
110 | |> Seq.toList
111 | }
112 |
113 | member this.LocalBranches (repository : string) = async {
114 | let repo = new Repository(repository)
115 | return repo.Branches
116 | |> Seq.toList
117 | }
118 |
119 |
120 | interface IGit with
121 | member this.Clone (url : string) (directory : string) = async {
122 | do! Async.SwitchToThreadPool()
123 |
124 | let options = new CloneOptions()
125 | options.IsBare <- true;
126 | options.OnTransferProgress <- new LibGit2Sharp.Handlers.TransferProgressHandler(fun p ->
127 | let message =
128 | "Cloning " + url + " " + p.ReceivedObjects.ToString() +
129 | "(" + p.IndexedObjects.ToString() + ")" + " / " + p.TotalObjects.ToString()
130 | console.Write(message, LoggingLevel.Trace)
131 | true
132 | )
133 |
134 | options.CredentialsProvider <- credentialsHandler
135 |
136 | Repository.Clone (url, directory, options) |> ignore
137 | }
138 |
139 | member this.ShallowClone (url : string) (directory : string) = async {
140 | return! (this :> IGit).Clone url directory
141 | }
142 |
143 | member this.HasCommit (gitPath : string) (revision : Revision) = async {
144 | do! Async.SwitchToThreadPool()
145 | let repo = new Repository (gitPath)
146 | let commit = repo.Lookup(revision)
147 | return
148 | match commit with
149 | | null -> false
150 | | _ -> true
151 | }
152 |
153 | member this.DefaultBranch (gitPath : string) = async {
154 | let repo = new Repository (gitPath)
155 | return repo.Head.CanonicalName
156 | }
157 |
158 | member this.Unshallow (gitDir : string) = async {
159 | do! Async.SwitchToThreadPool()
160 | let repo = new Repository (gitDir)
161 | let options = new FetchOptions()
162 | options.CredentialsProvider <- credentialsHandler
163 | Commands.Fetch(repo, "origin", ["+refs/heads/*:refs/heads/*"; "+refs/tags/*:refs/tags/*"], options, "")
164 | }
165 |
166 | member this.UpdateRefs (gitDir : string) = (this :> IGit).Unshallow gitDir
167 |
168 | member this.Checkout (gitDir : string) (revision : string) = async {
169 | do! Async.SwitchToThreadPool()
170 |
171 | let repo = new Repository (gitDir)
172 | let options = new CheckoutOptions()
173 |
174 | options.OnCheckoutProgress <- new LibGit2Sharp.Handlers.CheckoutProgressHandler(fun (msg) (i) (n) ->
175 | let message = "Checking out " + revision + " " + msg + " " + i.ToString() + " / " + n.ToString()
176 | console.Write(message, LoggingLevel.Debug)
177 | )
178 |
179 | Commands.Checkout(repo, revision, options) |> ignore
180 | }
181 |
182 | member this.CheckoutTo (gitPath : string) (revision : Revision) (installPath : string) = async {
183 | let! exists = Files.directoryExists (installPath)
184 | if not exists then
185 | do! Files.mkdirp installPath
186 | do! sharedGitClone gitPath installPath
187 | do! (this :> IGit).UpdateRefs installPath
188 | return! (this :> IGit).Checkout installPath revision
189 | }
190 |
191 | member this.FetchBranch (repository : String) (branch : Buckaroo.Branch) (_ : int) = async {
192 | do! Async.SwitchToThreadPool()
193 | let repo = new Repository (repository)
194 | let options = new FetchOptions()
195 | options.CredentialsProvider <- credentialsHandler
196 | Commands.Fetch(repo, "origin", [branch + ":" + branch], options, "")
197 | }
198 |
199 | member this.FetchCommits (repository : String) (branch : Buckaroo.Branch) : AsyncSeq = asyncSeq {
200 | do! (this :> IGit).FetchBranch repository branch 0
201 | do! Async.SwitchToThreadPool()
202 | let repo = new Repository (repository)
203 | let filter = new CommitFilter()
204 | filter.IncludeReachableFrom <- branch
205 | return
206 | seq {
207 | for x in repo.Commits.QueryBy (filter) do
208 | yield x.Sha
209 | }
210 | |> Seq.toList
211 | }
212 |
213 | member this.RemoteRefs (url : String) = async {
214 | do! Async.SwitchToThreadPool ()
215 |
216 | return Repository.ListRemoteReferences(url)
217 | |> Seq.filter(fun ref -> isHead ref || isTag ref)
218 | |> Seq.map(fun ref ->
219 | match isTag ref with
220 | | true -> {
221 | Type = RefType.Tag
222 | Revision = ref.TargetIdentifier;
223 | Name = ref.CanonicalName.Substring("refs/tags/".Length);
224 | }
225 | | false -> {
226 | Type = RefType.Branch
227 | Revision = ref.TargetIdentifier;
228 | Name = ref.CanonicalName.Substring("refs/heads/".Length);
229 | }
230 | )
231 | |> Seq.toList
232 | }
233 |
234 | member this.ReadFile (repository : String) (commit : Revision) (path : String) = async {
235 | do! Async.SwitchToThreadPool()
236 |
237 | let repo = new Repository (repository)
238 | let blob = repo.Lookup(commit)
239 | let node = blob.[path].Target :?> Blob
240 |
241 | return node.GetContentText ()
242 | }
243 |
--------------------------------------------------------------------------------
/buckaroo/GitManager.fs:
--------------------------------------------------------------------------------
1 | namespace Buckaroo
2 |
3 | open System
4 | open System.IO
5 | open System.Security.Cryptography
6 | open System.Text.RegularExpressions
7 | open FSharp.Control
8 | open FSharpx
9 | open FSharpx.Control
10 | open Buckaroo.Console
11 | open Buckaroo.Logger
12 | open Buckaroo.RichOutput
13 |
14 | type GitManagerRequest =
15 | | CloneRequest of string * AsyncReplyChannel>
16 | | FetchRefs of string * AsyncReplyChannel>
17 |
18 | type FetchStyle =
19 | | RemoteFirst
20 | | CacheFirst
21 |
22 | type GitManager (style: FetchStyle, console : ConsoleManager, git : IGit, cacheDirectory : string) =
23 |
24 | let logger = createLogger console (Some "git")
25 |
26 | let mutable refsCache = Map.empty
27 |
28 | let bytesToHex bytes =
29 | bytes
30 | |> Array.map (fun (x : byte) -> System.String.Format("{0:x2}", x))
31 | |> String.concat System.String.Empty
32 |
33 | let sanitizeFilename (x : string) =
34 | let regexSearch =
35 | new string(Path.GetInvalidFileNameChars()) +
36 | new string(Path.GetInvalidPathChars()) +
37 | "@.:\\/"
38 |
39 | let r = Regex(String.Format("[{0}]", Regex.Escape(regexSearch)))
40 |
41 | Regex.Replace(r.Replace(x, "-"), "-{2,}", "-")
42 |
43 | let cloneFolderName (url : string) =
44 | let bytes = System.Text.Encoding.UTF8.GetBytes url
45 | let hash =
46 | bytes
47 | |> (new SHA256Managed()).ComputeHash
48 | |> bytesToHex
49 | let folder = sanitizeFilename(url).ToLower() + "-" + hash.Substring(0, 16)
50 | Path.Combine(cacheDirectory, folder)
51 |
52 | let pickRefsToFetch (style: FetchStyle) (remote : Async]>) (cache: Async>) = async {
53 | match style with
54 | | RemoteFirst ->
55 | let! x = remote
56 | if x |> List.isEmpty |> not then
57 | return x
58 | else return! cache
59 | | CacheFirst ->
60 | let! x = cache
61 | if x |> List.isEmpty |> not then
62 | return x
63 | else return! remote
64 | }
65 |
66 | let mailboxCloneProcessor = MailboxProcessor.Start(fun inbox -> async {
67 | let mutable cloneCache : Map> = Map.empty
68 | while true do
69 | let! message = inbox.Receive()
70 | match message with
71 | | CloneRequest (url, replyChannel) ->
72 | match cloneCache |> Map.tryFind url with
73 | | Some task ->
74 | replyChannel.Reply(task)
75 | | None ->
76 | let targetDirectory = cloneFolderName url
77 | let task =
78 | async {
79 | if Directory.Exists targetDirectory |> not
80 | then
81 | do! git.ShallowClone url targetDirectory
82 | return targetDirectory
83 | }
84 | |> Async.Cache
85 | cloneCache <- cloneCache |> Map.add url task
86 | replyChannel.Reply(task)
87 | | FetchRefs (url, replyChannel) ->
88 | match refsCache |> Map.tryFind url with
89 | | Some task -> replyChannel.Reply (task)
90 | | None ->
91 | let task =
92 | async {
93 | logger.RichInfo ((text "Fetching refs from ") + (highlight url))
94 |
95 | let cacheDir = cloneFolderName url
96 | let startTime = System.DateTime.Now
97 |
98 | let! refs =
99 | pickRefsToFetch
100 | style
101 | (git.RemoteRefs url
102 | |> Async.Catch
103 | |> Async.map (Choice.toOption >> Option.defaultValue([])))
104 | (git.RemoteRefs cacheDir
105 | |> Async.Catch
106 | |> Async.map (Choice.toOption >> Option.defaultValue([])))
107 |
108 | let endTime = System.DateTime.Now
109 |
110 | if refs |> List.isEmpty then
111 | raise <| SystemException("No internet connection and the cache is empty")
112 |
113 | logger.RichSuccess(
114 | (text "Fetched ") +
115 | (refs |> List.length |> string |> info) +
116 | (text " refs in ") +
117 | ((endTime - startTime).TotalSeconds.ToString("N3")))
118 |
119 | return refs
120 | }
121 | |> Async.Cache
122 |
123 | refsCache <- refsCache |> Map.add url task
124 |
125 | replyChannel.Reply (task)
126 | })
127 |
128 | member this.Clone (url : string) : Async = async {
129 | let! res = mailboxCloneProcessor.PostAndAsyncReply(fun ch -> CloneRequest(url, ch))
130 | return! res
131 | }
132 |
133 | member this.CopyFromCache (gitUrl : string) (revision : Revision) (installPath : string) : Async = async {
134 | let! hasGit =
135 | Files.directoryExists (Path.Combine (installPath, ".git/"))
136 |
137 | if hasGit
138 | then
139 | do! git.UpdateRefs installPath
140 | return! git.Checkout installPath revision
141 | else
142 | do! git.CheckoutTo (cloneFolderName gitUrl) revision installPath
143 | }
144 |
145 | member this.FindCommit (url : string) (commit : string) (maybeBranchHint : Option) : Async = async {
146 | let! targetDirectory = this.Clone(url)
147 | let operations = asyncSeq {
148 | yield async { return () };
149 |
150 | yield git.UpdateRefs targetDirectory
151 |
152 | match maybeBranchHint with
153 | | Some branch ->
154 | yield
155 | git.FetchCommits targetDirectory branch
156 | |> AsyncSeq.takeWhile ( (<>) commit )
157 | |> AsyncSeq.toListAsync
158 | |> Async.Ignore
159 | | None ->
160 | let! defaultBranch = git.DefaultBranch targetDirectory
161 |
162 | yield
163 | AsyncSeq.interleave
164 | (if defaultBranch <> "master"
165 | then this.FetchCommits targetDirectory "master"
166 | else AsyncSeq.ofSeq [])
167 | (this.FetchCommits targetDirectory defaultBranch)
168 | |> AsyncSeq.takeWhile ( (<>) commit )
169 | |> AsyncSeq.toListAsync
170 | |> Async.Ignore
171 |
172 | yield git.Unshallow targetDirectory;
173 | }
174 |
175 | let! success =
176 | operations
177 | |> AsyncSeq.mapAsync id
178 | |> AsyncSeq.mapAsync (fun _ -> git.HasCommit targetDirectory commit)
179 | |> AsyncSeq.skipWhile not
180 | |> AsyncSeq.take 1
181 | |> AsyncSeq.lastOrDefault false
182 |
183 | if not success
184 | then
185 | raise <| Exception("Failed to fetch: " + url + " " + commit)
186 | }
187 |
188 | member this.FetchRefs (url : string) = async {
189 | let! res = mailboxCloneProcessor.PostAndAsyncReply(fun ch -> FetchRefs(url, ch))
190 | return! res
191 | }
192 |
193 | member this.GetFile (url : string) (revision : Revision) (file : string) : Async =
194 | async {
195 | let targetDirectory = cloneFolderName url
196 | // TODO: Preemptively clone and fetch
197 | return! git.ReadFile targetDirectory revision file
198 | }
199 |
200 | member this.FetchCommits (url : string) (branch : Branch) = asyncSeq {
201 | let! targetDirectory = this.Clone(url)
202 | yield! git.FetchCommits targetDirectory branch
203 | }
204 |
205 | member this.DefaultBranch (path) = async {
206 | return! git.DefaultBranch path
207 | }
208 |
--------------------------------------------------------------------------------
/buckaroo/Glob.fs:
--------------------------------------------------------------------------------
1 | module Buckaroo.Glob
2 |
3 | open System
4 | open Microsoft.Extensions.FileSystemGlobbing
5 |
6 | let private stripTrailingSlash (x : string) =
7 | if x.EndsWith "/"
8 | then x.Substring(0, x.Length - 1)
9 | else x
10 |
11 | let isLike (pattern : string) (x : string) =
12 | let matcher = new Matcher(StringComparison.OrdinalIgnoreCase)
13 | matcher.AddInclude pattern |> ignore
14 | let result = matcher.Match (stripTrailingSlash x)
15 | result.HasMatches
16 |
--------------------------------------------------------------------------------
/buckaroo/Hashing.fs:
--------------------------------------------------------------------------------
1 | module Buckaroo.Hashing
2 |
3 | open System.Security.Cryptography
4 |
5 | let private bytesToHex bytes =
6 | bytes
7 | |> Array.map (fun (x : byte) -> System.String.Format("{0:x2}", x))
8 | |> String.concat System.String.Empty
9 |
10 | let sha256 (x : string) =
11 | use hasher = new SHA256Managed()
12 | let bytes = System.Text.Encoding.UTF8.GetBytes x
13 | bytes
14 | |> hasher.ComputeHash
15 | |> bytesToHex
16 |
--------------------------------------------------------------------------------
/buckaroo/HelpCommand.fs:
--------------------------------------------------------------------------------
1 | module Buckaroo.HelpCommand
2 |
3 | open Buckaroo.Tasks
4 | open Buckaroo.RichOutput
5 | open System
6 |
7 | let task (context : TaskContext) = async {
8 | let log (x : string) = context.Console.Write(x)
9 |
10 | let logBash (x : string) =
11 | x
12 | |> text
13 | |> foreground ConsoleColor.Green
14 | |> context.Console.Write
15 |
16 | let logUrl (x : string) =
17 | x
18 | |> text
19 | |> foreground ConsoleColor.Cyan
20 | |> background ConsoleColor.Black
21 | |> context.Console.Write
22 |
23 | logBash("$ buckaroo init")
24 | log("Create a Buckaroo manifest in the current working directory. ")
25 | log("")
26 |
27 | logBash("$ buckaroo resolve")
28 | log("Generates a fresh lock-file from the existing manifest. ")
29 | log("")
30 |
31 | logBash("$ buckaroo install")
32 | log("Installs the packages as described in the current lock-file. ")
33 | log("")
34 |
35 | logBash("$ buckaroo add @...")
36 | log("Adds the given package(s) to the current manifest, updates the lock-file and installs it to the packages folder. ")
37 | log("If no satisfactory resolution can be found then nothing is changed. ")
38 | log("")
39 |
40 | logBash("$ buckaroo upgrade [ [ @ ] ]")
41 | log("Upgrades the given package(s) to the highest version that meets the constraints in the manifest. ")
42 | log("Optionally, a version can be specified to move the package to. ")
43 | log("If no packages are specified, then all are upgraded. ")
44 | log("")
45 |
46 | logBash("$ buckaroo remove ...")
47 | log("Removes an existing package from the manifest, updates the lock-file and deletes it from the packages folder. ")
48 | log("If no satisfactory resolution can be found then nothing is changed. ")
49 | log("")
50 |
51 | logBash("$ buckaroo version")
52 | log("Displays the version of this installation of Buckaroo. ")
53 | log("")
54 |
55 | logBash("$ buckaroo help")
56 | log("Displays this message. ")
57 | log("")
58 |
59 | log("For more information, visit: ")
60 | logUrl("https://github.com/LoopPerfect/buckaroo")
61 |
62 | do! context.Console.Flush()
63 |
64 | return 0
65 | }
66 |
--------------------------------------------------------------------------------
/buckaroo/Logger.fs:
--------------------------------------------------------------------------------
1 | module Buckaroo.Logger
2 |
3 | open System
4 | open Buckaroo.Console
5 | open Buckaroo.RichOutput
6 |
7 | type Logger =
8 | {
9 | Print : string -> Unit
10 | Info : string -> Unit
11 | RichInfo : RichOutput -> Unit
12 | Success : string -> Unit
13 | RichSuccess : RichOutput -> Unit
14 | Trace : string -> Unit
15 | Warning : string -> Unit
16 | RichWarning : RichOutput -> Unit
17 | Error : string -> Unit
18 | RichError : RichOutput -> Unit
19 | }
20 |
21 | let createLogger (console : ConsoleManager) (componentName : string option) =
22 | let componentPrefix =
23 | componentName
24 | |> Option.map (fun x -> "[" + x + "] " |> text |> foreground ConsoleColor.DarkGray)
25 | |> Option.defaultValue (text "")
26 |
27 | let print (x : string) =
28 | console.Write (componentPrefix + x, LoggingLevel.Info)
29 |
30 | let prefix =
31 | "info "
32 | |> text
33 | |> foreground ConsoleColor.Blue
34 |
35 | let info (x : string) =
36 | console.Write (componentPrefix + prefix + x, LoggingLevel.Info)
37 |
38 | let richInfo (x : RichOutput) =
39 | console.Write (componentPrefix + prefix + x, LoggingLevel.Info)
40 |
41 | let prefix =
42 | "success "
43 | |> text
44 | |> foreground ConsoleColor.Green
45 |
46 | let success (x : string) =
47 | console.Write (componentPrefix + prefix + x, LoggingLevel.Info)
48 |
49 | let richSuccess (x : RichOutput) =
50 | console.Write (componentPrefix + prefix + x, LoggingLevel.Info)
51 |
52 | let trace (x : string) =
53 | console.Write (componentPrefix + x, LoggingLevel.Trace)
54 |
55 | let prefix =
56 | "warning "
57 | |> text
58 | |> foreground ConsoleColor.Yellow
59 |
60 | let warning (x : string) =
61 | console.Write (componentPrefix + prefix + x, LoggingLevel.Info)
62 |
63 | let richWarning (x : RichOutput) =
64 | console.Write (componentPrefix + prefix + x, LoggingLevel.Info)
65 |
66 | let prefix =
67 | "error "
68 | |> text
69 | |> foreground ConsoleColor.Red
70 |
71 | let error (x : string) =
72 | console.Write (componentPrefix + prefix + x, LoggingLevel.Info)
73 |
74 | let richError (x : RichOutput) =
75 | console.Write (componentPrefix + prefix + x, LoggingLevel.Info)
76 |
77 | {
78 | Print = print
79 | Info = info
80 | RichInfo = richInfo
81 | Success = success
82 | RichSuccess = richSuccess
83 | Trace = trace
84 | Warning = warning
85 | RichWarning = richWarning
86 | Error = error
87 | RichError = richError
88 | }
89 |
--------------------------------------------------------------------------------
/buckaroo/Option.fs:
--------------------------------------------------------------------------------
1 | module Buckaroo.Option
2 |
3 | type OptionBuilder () =
4 | member this.Bind(x, f) =
5 | match x with
6 | | Option.Some o -> f o
7 | | Option.None -> Option.None
8 | member this.Return(value) = Option.Some value
9 | member this.ReturnFrom(value) = value
10 |
11 | let option = new OptionBuilder()
12 |
13 | let all xs =
14 | let folder = fun state next ->
15 | match (state, next) with
16 | | (Option.Some ys, Option.Some y) -> ys |> List.append [ y ] |> Option.Some
17 | | _ -> Option.None
18 | xs
19 | |> Seq.fold folder (Option.Some [])
20 |
--------------------------------------------------------------------------------
/buckaroo/Override.fs:
--------------------------------------------------------------------------------
1 | namespace Buckaroo
2 |
3 | type Override =
4 | {
5 | Package : PackageIdentifier
6 | Substitution : PackageIdentifier
7 | }
8 |
9 | module Override =
10 |
11 | open FSharpx.Result
12 | open Buckaroo.Toml
13 |
14 | let fromToml (toml : Nett.TomlObject) = result {
15 | let! table =
16 | toml
17 | |> Toml.asTable
18 |
19 | let! package =
20 | table
21 | |> Toml.get "package"
22 | |> Result.bind Toml.asString
23 | |> Result.bind (PackageIdentifier.parse >> Result.mapError TomlError.UnexpectedType)
24 |
25 | let! substitution =
26 | table
27 | |> Toml.get "substitution"
28 | |> Result.bind Toml.asString
29 | |> Result.bind (PackageIdentifier.parse >> Result.mapError TomlError.UnexpectedType)
30 |
31 | return
32 | {
33 | Package = package
34 | Substitution = substitution
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/buckaroo/OverrideSourceExplorer.fs:
--------------------------------------------------------------------------------
1 | namespace Buckaroo
2 |
3 | type OverrideSourceExplorer (sourceExplorer : ISourceExplorer, overrides : Map) =
4 |
5 | interface ISourceExplorer with
6 |
7 | member this.FetchVersions locations package =
8 | let packageAfterOverrides =
9 | overrides
10 | |> Map.tryFind package
11 | |> Option.defaultValue package
12 | sourceExplorer.FetchVersions locations packageAfterOverrides
13 |
14 | member this.LockLocation packageLocation =
15 | sourceExplorer.LockLocation packageLocation
16 |
17 | member this.FetchLocations locations package version =
18 | let packageAfterOverrides =
19 | overrides
20 | |> Map.tryFind package
21 | |> Option.defaultValue package
22 | sourceExplorer.FetchLocations locations packageAfterOverrides version
23 |
24 | member this.FetchManifest (location, versions) =
25 | sourceExplorer.FetchManifest (location, versions)
26 |
27 | member this.FetchLock (location, versions) =
28 | sourceExplorer.FetchLock (location, versions)
29 |
--------------------------------------------------------------------------------
/buckaroo/PackageIdentifier.fs:
--------------------------------------------------------------------------------
1 | namespace Buckaroo
2 |
3 | type AdhocPackageIdentifier = { Owner : string; Project : string }
4 |
5 | type GitLabPackageIdentifier = { Groups : string list; Project : string }
6 |
7 | type PackageIdentifier =
8 | | GitHub of AdhocPackageIdentifier
9 | | BitBucket of AdhocPackageIdentifier
10 | | GitLab of GitLabPackageIdentifier
11 | | Adhoc of AdhocPackageIdentifier
12 |
13 | module PackageIdentifier =
14 |
15 | open FParsec
16 | open Buckaroo.RichOutput
17 |
18 | let show (id : PackageIdentifier) =
19 | match id with
20 | | GitHub x -> "github.com/" + x.Owner + "/" + x.Project
21 | | BitBucket x -> "bitbucket.org/" + x.Owner + "/" + x.Project
22 | | GitLab x -> "gitlab.com/" + (x.Groups |> String.concat "/") + "/" + x.Project
23 | | Adhoc x -> x.Owner + "/" + x.Project
24 |
25 | let showRich (id : PackageIdentifier) =
26 | id
27 | |> show
28 | |> identifier
29 |
30 | let private gitHubIdentifierParser =
31 | CharParsers.regex @"[a-zA-Z.\d](?:[a-zA-Z_.\d]|-(?=[a-zA-Z_.\d])){0,38}"
32 |
33 | let adhocPackageIdentifierParser = parse {
34 | let! owner = gitHubIdentifierParser
35 | do! CharParsers.skipString "/"
36 | let! project = gitHubIdentifierParser
37 | return { Owner = owner.ToLower(); Project = project.ToLower() }
38 | }
39 |
40 | let parseAdhocIdentifier (x : string) : Result =
41 | match run (adhocPackageIdentifierParser .>> CharParsers.eof) x with
42 | | Success(result, _, _) -> Result.Ok result
43 | | Failure(error, _, _) -> Result.Error error
44 |
45 | let gitHubPackageIdentifierParser = parse {
46 | do! CharParsers.skipString "github.com/" <|> CharParsers.skipString "github+"
47 | let! owner = gitHubIdentifierParser
48 | do! CharParsers.skipString "/"
49 | let! project = gitHubIdentifierParser
50 | return { Owner = owner.ToLower(); Project = project.ToLower() }
51 | }
52 |
53 | let parseGitHubIdentifier (x : string) =
54 | match run (gitHubPackageIdentifierParser .>> CharParsers.eof) x with
55 | | Success(result, _, _) -> Result.Ok result
56 | | Failure(error, _, _) -> Result.Error error
57 |
58 | let bitBucketPackageIdentifierParser = parse {
59 | do! CharParsers.skipString "bitbucket.org/" <|> CharParsers.skipString "bitbucket+"
60 | let! owner = gitHubIdentifierParser
61 | do! CharParsers.skipString "/"
62 | let! project = gitHubIdentifierParser
63 | return { Owner = owner.ToLower(); Project = project.ToLower() }
64 | }
65 |
66 | let parseBitBucketIdentifier (x : string) =
67 | match run (bitBucketPackageIdentifierParser .>> CharParsers.eof) x with
68 | | Success(result, _, _) -> Result.Ok result
69 | | Failure(error, _, _) -> Result.Error error
70 |
71 | let gitLabPackageIdentifierParser = parse {
72 | do! CharParsers.skipString "gitlab.com/"
73 | let! x = gitHubIdentifierParser
74 | do! CharParsers.skipString "/"
75 | let! xs = Primitives.sepBy1 gitHubIdentifierParser (skipString "/")
76 | let parts =
77 | [ x ] @ xs
78 | |> List.map (fun x -> x.ToLower ())
79 |
80 | return {
81 | Groups = parts |> List.truncate (parts.Length - 1)
82 | Project = parts |> List.last
83 | }
84 | }
85 |
86 | let parseGitLabIdentifier (x : string) =
87 | match run (gitLabPackageIdentifierParser .>> CharParsers.eof) x with
88 | | Success(result, _, _) -> Result.Ok result
89 | | Failure(error, _, _) -> Result.Error error
90 |
91 | let parser =
92 | gitHubPackageIdentifierParser |>> PackageIdentifier.GitHub
93 | <|> (bitBucketPackageIdentifierParser |>> PackageIdentifier.BitBucket)
94 | <|> (gitLabPackageIdentifierParser |>> PackageIdentifier.GitLab)
95 | <|> (adhocPackageIdentifierParser |>> PackageIdentifier.Adhoc)
96 |
97 | let parse (x : string) : Result =
98 | match run (parser .>> CharParsers.eof) x with
99 | | Success(result, _, _) -> Result.Ok result
100 | | Failure(error, _, _) -> Result.Error error
101 |
--------------------------------------------------------------------------------
/buckaroo/PackageLocation.fs:
--------------------------------------------------------------------------------
1 | namespace Buckaroo
2 |
3 | type HttpLocation = {
4 | Url : string
5 | StripPrefix : string option
6 | Type : ArchiveType option
7 | }
8 |
9 | type GitLocation = {
10 | Url : string
11 | Revision : Revision
12 | }
13 |
14 | type GitLabLocation = {
15 | Package : GitLabPackageIdentifier
16 | Revision : Revision
17 | }
18 |
19 | type HostedGitLocation = {
20 | Package : AdhocPackageIdentifier
21 | Revision : Revision
22 | }
23 |
24 | type PackageLocation =
25 | | Http of HttpLocation
26 | | Git of GitLocation
27 | | GitHub of HostedGitLocation
28 | | BitBucket of HostedGitLocation
29 | | GitLab of GitLabLocation
30 |
31 | override this.ToString () =
32 | match this with
33 | | Http http -> http.Url + "#" + (http.StripPrefix |> Option.defaultValue "")
34 | | Git git -> git.Url + "#" + git.Revision
35 | | GitHub gitHub -> "github.com/" + gitHub.Package.Owner + "/" + gitHub.Package.Project + "#" + gitHub.Revision
36 | | BitBucket bitbucket -> "bitbucket.org/" + bitbucket.Package.Owner + "/" + bitbucket.Package.Project + "#" + bitbucket.Revision
37 | | GitLab gitLab -> "gitlab.com/" + (gitLab.Package.Groups |> String.concat "/") + "/" + gitLab.Package.Project + "#" + gitLab.Revision
38 |
39 | module PackageLocation =
40 |
41 | open System
42 |
43 | let gitHubUrl (x : AdhocPackageIdentifier) =
44 | if Environment.GetEnvironmentVariable "BUCKAROO_GITHUB_SSH" |> isNull
45 | then
46 | "https://github.com/" + x.Owner + "/" + x.Project + ".git"
47 | else
48 | "git@github.com:" + x.Owner + "/" + x.Project + ".git"
49 |
50 | let bitBucketUrl (x : AdhocPackageIdentifier) =
51 | if Environment.GetEnvironmentVariable "BUCKAROO_BITBUCKET_SSH" |> isNull
52 | then
53 | "https://bitbucket.org/" + x.Owner + "/" + x.Project + ".git"
54 | else
55 | "git@bitbucket.org:" + x.Owner + "/" + x.Project + ".git"
56 |
57 | let gitLabUrl (x : GitLabPackageIdentifier) =
58 | if Environment.GetEnvironmentVariable "BUCKAROO_GITLAB_SSH" |> isNull
59 | then
60 | "https://gitlab.com/" + (x.Groups |> String.concat "/") + "/" + x.Project + ".git"
61 | else
62 | "git@gitlab.com:" + (x.Groups |> String.concat "/") + "/" + x.Project + ".git"
63 |
64 | let versionSetFromLocation location =
65 | match location with
66 | | PackageLocation.GitHub g -> Set [Version.Git (GitVersion.Revision g.Revision)]
67 | | PackageLocation.Git g -> Set [Version.Git (GitVersion.Revision g.Revision)]
68 | | PackageLocation.GitLab g -> Set [Version.Git (GitVersion.Revision g.Revision)]
69 | | PackageLocation.BitBucket g -> Set [Version.Git (GitVersion.Revision g.Revision)]
70 | | _ -> Set.empty
71 |
--------------------------------------------------------------------------------
/buckaroo/PackageLock.fs:
--------------------------------------------------------------------------------
1 | namespace Buckaroo
2 |
3 | open Buckaroo
4 |
5 | type PackageLock =
6 | | Http of HttpLocation * string
7 | | Git of GitLocation
8 | | GitHub of HostedGitLocation
9 | | BitBucket of HostedGitLocation
10 | | GitLab of GitLabLocation
11 |
12 | module PackageLock =
13 |
14 | open FSharpx.Result
15 |
16 | let toLocation (x : PackageLock) : PackageLocation =
17 | match x with
18 | | Http (location, sha256) -> PackageLocation.Http location
19 | | Git git -> PackageLocation.Git git
20 | | GitHub git -> PackageLocation.GitHub git
21 | | BitBucket git -> PackageLocation.BitBucket git
22 | | GitLab git -> PackageLocation.GitLab git
23 |
24 | let show (x : PackageLock) =
25 | match x with
26 | | Git g -> g.Url + "#" + g.Revision
27 | | Http (y, _) ->
28 | y.Url +
29 | (y.StripPrefix |> Option.map ((+) "#") |> Option.defaultValue "") +
30 | (y.Type |> Option.map (ArchiveType.show) |> Option.map (fun x -> "#" + x) |> Option.defaultValue "")
31 | | GitHub y -> (PackageLocation.gitHubUrl y.Package) + "#" + y.Revision
32 | | BitBucket y -> (PackageLocation.bitBucketUrl y.Package) + "#" + y.Revision
33 | | GitLab y -> (PackageLocation.gitLabUrl y.Package) + "#" + y.Revision
34 |
35 | let fromToml toml = result {
36 | match toml |> Toml.tryGet "git" with
37 | | Some gitToml ->
38 | let! git =
39 | gitToml
40 | |> Toml.asString
41 | |> Result.mapError Toml.TomlError.show
42 |
43 | let! revision =
44 | toml
45 | |> Toml.get "revision"
46 | |> Result.bind Toml.asString
47 | |> Result.mapError Toml.TomlError.show
48 |
49 | return PackageLock.Git { Url = git; Revision = revision }
50 | | None ->
51 | match toml |> Toml.tryGet "package" with
52 | | Some tomlPackage ->
53 | let! package =
54 | tomlPackage
55 | |> Toml.asString
56 | |> Result.mapError Toml.TomlError.show
57 | |> Result.bind PackageIdentifier.parse
58 |
59 | let! revision =
60 | toml
61 | |> Toml.get "revision"
62 | |> Result.bind Toml.asString
63 | |> Result.mapError Toml.TomlError.show
64 |
65 | match package with
66 | | PackageIdentifier.Adhoc _ ->
67 | return! Result.Error "Expected a hosted-git package"
68 | | PackageIdentifier.GitHub x ->
69 | return PackageLock.GitHub { Package = x; Revision = revision }
70 | | PackageIdentifier.BitBucket x ->
71 | return PackageLock.BitBucket { Package = x; Revision = revision }
72 | | PackageIdentifier.GitLab x ->
73 | return PackageLock.GitLab { Package = x; Revision = revision }
74 | | None ->
75 | let! url =
76 | toml
77 | |> Toml.get "url"
78 | |> Result.bind Toml.asString
79 | |> Result.mapError Toml.TomlError.show
80 |
81 | let! sha256 =
82 | toml
83 | |> Toml.get "sha256"
84 | |> Result.bind Toml.asString
85 | |> Result.mapError Toml.TomlError.show
86 |
87 | let! stripPrefix =
88 | toml
89 | |> Toml.tryGet "strip_prefix"
90 | |> Option.map (Toml.asString)
91 | |> Option.map (Result.map Option.Some)
92 | |> Option.map (Result.mapError Toml.TomlError.show)
93 | |> Option.defaultValue (Result.Ok Option.None)
94 |
95 | let! archiveType =
96 | toml
97 | |> Toml.tryGet "type"
98 | |> Option.map (Toml.asString)
99 | |> Option.map (Result.mapError Toml.TomlError.show)
100 | |> Option.map (Result.bind (ArchiveType.parse >> Result.mapError ArchiveType.ParseError.show))
101 | |> Option.map (Result.map Option.Some)
102 | |> Option.defaultValue (Result.Ok Option.None)
103 |
104 | return
105 | PackageLock.Http
106 | ({ Url = url; StripPrefix = stripPrefix; Type = archiveType }, sha256)
107 | }
--------------------------------------------------------------------------------
/buckaroo/PackageSource.fs:
--------------------------------------------------------------------------------
1 | namespace Buckaroo
2 |
3 | type GitPackageSource = {
4 | Uri : string
5 | }
6 |
7 | type PackageSource =
8 | | Git of GitPackageSource
9 | | Http of Map
10 |
11 | module PackageSource =
12 | let show = function
13 | | Git g -> "{ git = " + g.Uri + " }"
14 | | Http h ->
15 | "{\n" + (
16 | h
17 | |> Map.toSeq
18 | |> Seq.map(fun (version, source) ->
19 | "Version = \"" + version.ToString() + "\"\n" +
20 | "Url = \"" + source.Url + "\"\n" +
21 | (source.StripPrefix
22 | |> Option.map(fun x -> "StripPrefix =\"" + x + "\"\n")
23 | |> Option.defaultValue("") ) +
24 | (source.Type
25 | |> Option.map(fun x -> "Type =\"" + x.ToString() + "\"\n")
26 | |> Option.defaultValue("") ) + "\n"
27 | )
28 | |> String.concat "\n") +
29 | "\n}"
--------------------------------------------------------------------------------
/buckaroo/Paths.fs:
--------------------------------------------------------------------------------
1 | module Buckaroo.Paths
2 |
3 | open System
4 | open System.IO
5 |
6 | let private sep = String [| Path.DirectorySeparatorChar |]
7 |
8 | let normalize (path : string) : string =
9 | if path = ""
10 | then
11 | "."
12 | else
13 | let rec loop (path : string) =
14 | let parts =
15 | path.Split (Path.DirectorySeparatorChar, StringSplitOptions.None)
16 | |> Seq.filter (fun x -> x <> ".")
17 | |> Seq.toList
18 |
19 | match parts with
20 | | ".."::".."::rest ->
21 | ".." + sep + ".." + sep + (rest |> String.concat sep |> loop)
22 | | _::".."::rest ->
23 | rest
24 | |> String.concat sep
25 | |> loop
26 | | x::rest ->
27 | if List.isEmpty rest
28 | then
29 | x
30 | else
31 | x + sep + (rest |> String.concat sep |> loop)
32 | | [] -> ""
33 | loop path
34 |
35 | let depth (path : string) =
36 | (normalize path).Split(Path.DirectorySeparatorChar).Length
37 |
38 | let combine x y = Path.Combine (x, y) |> normalize
39 |
40 | let rec combineAll xs =
41 | match xs with
42 | | [ x ] -> x
43 | | x::xs -> combine x (combineAll xs)
44 | | [] -> ""
45 |
--------------------------------------------------------------------------------
/buckaroo/Prefetch.fs:
--------------------------------------------------------------------------------
1 | module Buckaroo.Prefetch
2 |
3 | open FSharp.Control
4 |
5 | type private PrefetcherMessage =
6 | | Completed
7 | | Prefetch of PackageIdentifier
8 |
9 | type Prefetcher (sourceExplorer : ISourceExplorer, limit : int) =
10 | let agent = MailboxProcessor.Start (fun inbox ->
11 | let rec waiting () =
12 | inbox.Scan (fun x ->
13 | match x with
14 | | Completed -> Some (working (limit - 1))
15 | | _ -> None
16 | )
17 | and working inFlightCount = async {
18 | while true do
19 | let! message = inbox.Receive ()
20 |
21 | return!
22 | match message with
23 | | Completed -> working (inFlightCount - 1)
24 | | Prefetch package ->
25 | async {
26 | try
27 | do!
28 | sourceExplorer.FetchVersions Map.empty package
29 | |> AsyncSeq.tryFirst
30 | |> Async.Catch
31 | |> Async.Ignore
32 |
33 | finally
34 | inbox.Post (Completed)
35 | }
36 | |> Async.Start
37 |
38 | if inFlightCount < limit
39 | then
40 | working (inFlightCount + 1)
41 | else
42 | waiting ()
43 | }
44 |
45 | working 0
46 | )
47 |
48 | member this.Prefetch (package) = agent.Post (Prefetch package)
49 |
--------------------------------------------------------------------------------
/buckaroo/QuickstartCommand.fs:
--------------------------------------------------------------------------------
1 | module Buckaroo.QuickstartCommand
2 |
3 | open System
4 | open System.Text.RegularExpressions
5 | open Buckaroo.Tasks
6 |
7 | let private defaultBuck (libraryName : string) =
8 | [
9 | "load('//:buckaroo_macros.bzl', 'buckaroo_deps')";
10 | "";
11 | "cxx_library(";
12 | " name = '" + libraryName + "', ";
13 | " header_namespace = '" + libraryName + "', ";
14 | " exported_headers = subdir_glob([";
15 | " ('include', '**/*.hpp'), ";
16 | " ('include', '**/*.h'), ";
17 | " ]), ";
18 | " headers = subdir_glob([";
19 | " ('private_include', '**/*.hpp'), ";
20 | " ('private_include', '**/*.h'), ";
21 | " ]), ";
22 | " srcs = glob([";
23 | " 'src/**/*.cpp', ";
24 | " ]), ";
25 | " deps = buckaroo_deps(), ";
26 | " visibility = [";
27 | " 'PUBLIC', ";
28 | " ], ";
29 | ")";
30 | "";
31 | "cxx_binary(";
32 | " name = 'app', ";
33 | " srcs = [";
34 | " 'main.cpp', ";
35 | " ], ";
36 | " deps = [";
37 | " '//:" + libraryName + "', ";
38 | " ], ";
39 | ")";
40 | "";
41 | ]
42 | |> String.concat "\n"
43 |
44 | let private defaultBuckconfig =
45 | [
46 | "[project]";
47 | " ignore = .git";
48 | "";
49 | "[cxx]";
50 | " should_remap_host_platform = true";
51 | ""
52 | ]
53 | |> String.concat "\n"
54 |
55 | let private defaultMain =
56 | [
57 | "#include ";
58 | "";
59 | "int main() {";
60 | " std::cout << \"Hello, world. \" << std::endl; ";
61 | "";
62 | " return 0; ";
63 | "}";
64 | "";
65 | ]
66 | |> String.concat "\n"
67 |
68 | let isValidProjectName (candidate : string) =
69 | (Regex(@"^[A-Za-z0-9\-_]{2,32}$")).IsMatch(candidate) &&
70 | candidate.ToLower () <> "app"
71 |
72 | let requestProjectName (context : TaskContext) = async {
73 | let mutable candidate = ""
74 |
75 | while isValidProjectName candidate |> not do
76 | context.Console.Write("Please enter a project name (alphanumeric + underscores + dashes): ")
77 | let! input = context.Console.Read()
78 | candidate <- input.Trim()
79 |
80 | return candidate
81 | }
82 |
83 | let task (context : Tasks.TaskContext) = async {
84 | let! projectName = requestProjectName context
85 |
86 | context.Console.Write("Writing project files... ")
87 |
88 | do! Tasks.writeManifest Manifest.zero
89 | do! Files.mkdirp "src"
90 | do! Files.mkdirp "include"
91 | do! Files.mkdirp "private_include"
92 | do! Files.writeFile ".buckconfig" defaultBuckconfig
93 | do! Files.writeFile "BUCK" (defaultBuck projectName)
94 | do! Files.writeFile "main.cpp" defaultMain
95 |
96 | do! ResolveCommand.task context Solution.empty ResolutionStyle.Quick |> Async.Ignore
97 | do! InstallCommand.task context |> Async.Ignore
98 |
99 | context.Console.Write("To start your app: ")
100 | context.Console.Write(
101 | "$ buck run :app"
102 | |> RichOutput.text
103 | |> RichOutput.foreground ConsoleColor.Green
104 | )
105 | context.Console.Write("")
106 |
107 | return 0
108 | }
109 |
--------------------------------------------------------------------------------
/buckaroo/RemoveCommand.fs:
--------------------------------------------------------------------------------
1 | module Buckaroo.RemoveCommand
2 |
3 | open System
4 | open Buckaroo.RichOutput
5 |
6 | let task (context : Tasks.TaskContext) (packages : List) = async {
7 | context.Console.Write(
8 | (text "Removing [ ") +
9 | (packages |> Seq.map PackageIdentifier.showRich |> RichOutput.concat (text " ")) +
10 | " ]... "
11 | )
12 |
13 | let! manifest = Tasks.readManifest "."
14 |
15 | let newManifest =
16 | packages
17 | |> Seq.fold Manifest.remove manifest
18 |
19 | if manifest = newManifest
20 | then
21 | context.Console.Write("No changes to be made. " |> text |> foreground ConsoleColor.Green)
22 | return 0
23 | else
24 | let! maybeLock = Tasks.readLockIfPresent
25 | let! resolution = Solver.solve context Solution.empty newManifest ResolutionStyle.Quick maybeLock
26 |
27 | match resolution with
28 | | Ok solution ->
29 | let newLock = Lock.fromManifestAndSolution newManifest solution
30 |
31 | let lock =
32 | maybeLock
33 | |> Option.defaultValue { newLock with Packages = Map.empty }
34 |
35 | context.Console.Write(Lock.showDiff lock newLock)
36 |
37 | let removedPackages =
38 | lock.Packages
39 | |> Map.toSeq
40 | |> Seq.filter (fun (package, _) -> newLock.Packages |> Map.containsKey package |> not)
41 |
42 | for (package, _) in removedPackages do
43 | let path = InstallCommand.packageInstallPath [] package
44 | context.Console.Write("Deleting " + path + "... ")
45 | Files.deleteDirectoryIfExists path |> ignore
46 |
47 | do! Tasks.writeManifest newManifest
48 | do! Tasks.writeLock newLock
49 |
50 | let! install = InstallCommand.task context
51 |
52 | if install = 0
53 | then
54 | context.Console.Write("Done. " |> text |> foreground ConsoleColor.Green)
55 | return 0
56 | else
57 | context.Console.Write("Failed. " |> text |> foreground ConsoleColor.Red)
58 | return 1
59 | | x ->
60 | context.Console.Write(string x)
61 | context.Console.Write("No changes were written. " |> text |> foreground ConsoleColor.Green)
62 | return 0
63 | }
64 |
--------------------------------------------------------------------------------
/buckaroo/Resolution.fs:
--------------------------------------------------------------------------------
1 | namespace Buckaroo
2 |
3 | type Solution = {
4 | Resolutions : Map
5 | }
6 |
7 | type ResolutionStyle =
8 | | Quick
9 | | Upgrading
10 |
11 | module Solution =
12 |
13 | let empty = {
14 | Resolutions = Map.empty
15 | }
16 |
17 | type SolutionMergeError =
18 | | Conflict of PackageIdentifier
19 |
20 | let merge (a : Solution) (b : Solution) =
21 | let folder state (key, value) =
22 | match state with
23 | | Result.Ok solution ->
24 | match solution.Resolutions |> Map.tryFind key with
25 | | Some v ->
26 | if value = v
27 | then state
28 | else Result.Error (Conflict key)
29 | | None ->
30 | {
31 | solution with
32 | Resolutions =
33 | solution.Resolutions |> Map.add key value
34 | }
35 | |> Result.Ok
36 | | Result.Error _ -> state
37 | a.Resolutions |> Map.toSeq |> Seq.fold folder (Result.Ok b)
38 |
39 | let show (solution : Solution) =
40 | let rec f solution depth =
41 | let indent = "|" + ("_" |> String.replicate depth)
42 | solution.Resolutions
43 | |> Map.toSeq
44 | |> Seq.map (fun (k, v) ->
45 | let (resolvedVersion, subSolution) = v
46 | indent + (PackageIdentifier.show k) + "@" + (string resolvedVersion) +
47 | (f subSolution (depth + 1))
48 | )
49 | |> String.concat "\n"
50 | f solution 0
51 |
--------------------------------------------------------------------------------
/buckaroo/ResolveCommand.fs:
--------------------------------------------------------------------------------
1 | module Buckaroo.ResolveCommand
2 |
3 | open System
4 | open Buckaroo.RichOutput
5 | open Buckaroo.Logger
6 | open Buckaroo.SearchStrategy
7 |
8 | let task (context : Tasks.TaskContext) partialSolution resolutionStyle = async {
9 | let logger = createLogger context.Console None
10 |
11 | let! maybeLock = async {
12 | try
13 | return!
14 | Tasks.readLockIfPresent
15 | with error ->
16 | logger.Error "The existing lock-file is invalid. "
17 | logger.RichInfo (
18 | (text "Perhaps you want to delete ") +
19 | (text "buckaroo.lock.toml" |> foreground ConsoleColor.Magenta) +
20 | (text " and try again?")
21 | )
22 |
23 | return!
24 | raise error
25 | }
26 |
27 | let! manifest = Tasks.readManifest "."
28 |
29 | let resolve = async {
30 | let resolveStart = DateTime.Now
31 |
32 | logger.RichInfo <| (text "Resolve start: ") + (resolveStart |> Toml.formatDateTime |> text |> foreground ConsoleColor.Cyan)
33 |
34 | let styleName =
35 | match resolutionStyle with
36 | | Quick -> "quick"
37 | | Upgrading -> "upgrade"
38 | |> text
39 | |> foreground ConsoleColor.Cyan
40 |
41 | (text "Resolving dependencies using ") + (styleName) + " strategy... " |> logger.RichInfo
42 |
43 | let! resolution =
44 | Solver.solve context partialSolution manifest resolutionStyle maybeLock
45 |
46 | let resolveEnd = DateTime.Now
47 |
48 | logger.RichInfo <| (text "Resolve end: ") + (resolveEnd |> Toml.formatDateTime |> text |> foreground ConsoleColor.Cyan)
49 | logger.RichInfo <| (text "Resolve time: ") + (resolveEnd - resolveStart |> string |> text |> foreground ConsoleColor.Cyan)
50 |
51 | match resolution with
52 | | Result.Error e ->
53 | (SearchStrategyError.show e) |> logger.RichError
54 |
55 | return false
56 | | Result.Ok solution ->
57 | "A solution to the constraints was found. " |> logger.Success
58 | let lock = Lock.fromManifestAndSolution manifest solution
59 |
60 | try
61 | let! previousLock = Tasks.readLock
62 | let diff = Lock.showDiff previousLock lock
63 | diff |> text |> logger.RichInfo
64 | with _ ->
65 | ()
66 |
67 | do! Tasks.writeLock lock
68 |
69 | "The lock-file was updated. " |> logger.Success
70 |
71 | return true
72 | }
73 |
74 | match (resolutionStyle, maybeLock) with
75 | | (Quick, Some lock) ->
76 | if lock.ManifestHash = Manifest.hash manifest
77 | then
78 | logger.RichInfo <| (text "The existing lock-file is already up-to-date! ")
79 |
80 | return true
81 | else
82 | return! resolve
83 | | (_, _) ->
84 | return! resolve
85 | }
86 |
--------------------------------------------------------------------------------
/buckaroo/ResolvedVersion.fs:
--------------------------------------------------------------------------------
1 | namespace Buckaroo
2 |
3 | type ResolvedVersion = {
4 | Versions : Set
5 | Lock : PackageLock
6 | Manifest : Manifest
7 | }
8 |
--------------------------------------------------------------------------------
/buckaroo/Result.fs:
--------------------------------------------------------------------------------
1 | module Buckaroo.Result
2 |
3 | let isOk x =
4 | match x with
5 | | Ok _ -> true
6 | | Error _ -> false
7 |
8 | let optionToResult error x =
9 | match x with
10 | | Some x -> Result.Ok x
11 | | None -> Result.Error error
12 |
13 | let all xs =
14 | let folder = fun state next ->
15 | match (state, next) with
16 | | (Result.Ok ys, Result.Ok y) -> ys |> List.append [ y ] |> Result.Ok
17 | | (Result.Error e, _) -> Result.Error e
18 | | (_, Result.Error e) -> Result.Error e
19 | xs
20 | |> Seq.fold folder (Result.Ok [])
21 |
--------------------------------------------------------------------------------
/buckaroo/RichOutput.fs:
--------------------------------------------------------------------------------
1 | module Buckaroo.RichOutput
2 |
3 | type Segment =
4 | {
5 | Text : string;
6 | Foreground : System.ConsoleColor option;
7 | Background : System.ConsoleColor option;
8 | }
9 |
10 | type RichOutput =
11 | {
12 | Segments : Segment list
13 | }
14 | override this.ToString() =
15 | this.Segments
16 | |> Seq.map (fun x -> x.Text)
17 | |> String.concat ""
18 |
19 | static member (+) (a, b : string) =
20 | {
21 | a
22 | with
23 | Segments =
24 | a.Segments
25 | @ [ { Text = b; Foreground = None; Background = None } ]
26 | }
27 |
28 | static member (+) (a, b : Segment) =
29 | {
30 | a
31 | with
32 | Segments = a.Segments @ [ b ]
33 | }
34 |
35 | static member (+) (a, b : RichOutput) =
36 | {
37 | a
38 | with
39 | Segments = a.Segments @ b.Segments
40 | }
41 |
42 | static member (+) (a : string, b : RichOutput) =
43 | {
44 | b with
45 | Segments =
46 | [
47 | {
48 | Foreground = None
49 | Background = None
50 | Text = a
51 | }
52 | ] @ b.Segments
53 | }
54 |
55 | let zero = []
56 |
57 | let length richOutput =
58 | richOutput.Segments
59 | |> Seq.sumBy (fun x -> x.Text.Length)
60 |
61 | let text s = {
62 | Segments =
63 | [
64 | {
65 | Text = s;
66 | Foreground = None;
67 | Background = None;
68 | }
69 | ]
70 | }
71 |
72 | let foreground color richOutput =
73 | {
74 | richOutput
75 | with
76 | Segments =
77 | richOutput.Segments
78 | |> List.map (fun x -> {
79 | x with Foreground = Some color;
80 | })
81 | }
82 |
83 | let noForeground richOutput =
84 | {
85 | richOutput
86 | with
87 | Segments =
88 | richOutput.Segments
89 | |> List.map (fun x -> {
90 | x with Foreground = None;
91 | })
92 | }
93 |
94 | let background color richOutput =
95 | {
96 | richOutput
97 | with
98 | Segments =
99 | richOutput.Segments
100 | |> List.map (fun x -> {
101 | x with Background = Some color;
102 | })
103 | }
104 |
105 | let noBackground richOutput =
106 | {
107 | richOutput
108 | with
109 | Segments =
110 | richOutput.Segments
111 | |> List.map (fun x -> {
112 | x with Background = None;
113 | })
114 | }
115 |
116 | let concat sep xs =
117 | let rec loop sep xs =
118 | match xs with
119 | | [ x ] -> x
120 | | head::tail -> (head + sep + (loop sep tail))
121 | | [] -> text ""
122 | loop sep (Seq.toList xs)
123 |
124 |
125 | let subtle = text >> (foreground System.ConsoleColor.DarkGray)
126 | let warn = text >> (foreground System.ConsoleColor.Yellow)
127 | let info = text >> (foreground System.ConsoleColor.Cyan)
128 | let success = text >> (foreground System.ConsoleColor.Green)
129 | let highlight = text >> (foreground System.ConsoleColor.White)
130 | let identifier = text >> (foreground System.ConsoleColor.Magenta)
131 |
--------------------------------------------------------------------------------
/buckaroo/SearchStrategy.fs:
--------------------------------------------------------------------------------
1 | module Buckaroo.SearchStrategy
2 |
3 | type PackageConstraint = PackageIdentifier * Set
4 |
5 | type LocatedVersionSet = PackageLocation * Set
6 |
7 | type SearchStrategyError =
8 | | LimitReached of PackageConstraint * int
9 | | Unresolvable of PackageConstraint
10 | | NoManifest of PackageIdentifier
11 | | NoPrivateSolution of PackageIdentifier
12 | | TransitiveConflict of Set * SearchStrategyError>
13 |
14 | module SearchStrategyError =
15 |
16 | open System
17 | open Buckaroo.RichOutput
18 |
19 | let private showPackage p =
20 | p
21 | |> PackageIdentifier.show
22 | |> text
23 | |> foreground ConsoleColor.Blue
24 |
25 | let private showConstraint c =
26 | c
27 | |> Constraint.simplify
28 | |> Constraint.show
29 | |> text
30 | |> foreground ConsoleColor.Blue
31 |
32 | let private showCore (p, cs) =
33 | (showPackage p) + " at " + (showConstraint (All cs))
34 |
35 | let rec show (e : SearchStrategyError) =
36 | match e with
37 | | LimitReached ((p, c), l) ->
38 | "We reached the limit of " + (string l) + " consecutive failures for " +
39 | (showPackage p) + " at " +
40 | (showConstraint (All c)) + ". "
41 | | Unresolvable (p, c) ->
42 | "The package " + (showPackage p) + " at " + (showConstraint (All c)) + " is unresolvable. "
43 | | NoManifest p -> "We could not find any manifests for " + (showPackage p) + ". "
44 | | NoPrivateSolution p ->
45 | "We could not resolve a private dependency for " + (showPackage p) + "."
46 | | TransitiveConflict xs ->
47 | (text "We had the following conflicts: \n") +
48 | (
49 | xs
50 | |> Seq.collect (fun (cores, reason) ->
51 | cores
52 | |> Seq.map (fun core ->
53 | (" " + (core |> showCore) + ": ") + (show reason)
54 | )
55 | )
56 | |> RichOutput.concat (text "\n")
57 | )
58 |
--------------------------------------------------------------------------------
/buckaroo/SemVer.fs:
--------------------------------------------------------------------------------
1 | namespace Buckaroo
2 |
3 | type SemVer = { Major : int; Minor : int; Patch : int; Increment : int }
4 |
5 | module SemVer =
6 |
7 | open System
8 | open FParsec
9 |
10 | let zero : SemVer = { Major = 0; Minor = 0; Patch = 0; Increment = 0 }
11 |
12 | let create (major, minor, patch, increment) =
13 | {
14 | zero with
15 | Major = major;
16 | Minor = minor;
17 | Patch = patch;
18 | Increment = increment;
19 | }
20 |
21 | let compare (x : SemVer) (y : SemVer) =
22 | match x.Major.CompareTo y.Major with
23 | | 0 ->
24 | match x.Minor.CompareTo y.Minor with
25 | | 0 ->
26 | match x.Patch.CompareTo y.Patch with
27 | | 0 -> x.Increment.CompareTo y.Increment
28 | | c -> c
29 | | c -> c
30 | | c -> c
31 |
32 | let show (x : SemVer) : string =
33 | let elements =
34 | if x.Increment = 0
35 | then [ x.Major; x.Minor; x.Patch ]
36 | else [ x.Major; x.Minor; x.Patch; x.Increment ]
37 | elements
38 | |> Seq.map string
39 | |> String.concat "."
40 |
41 | let ToString (x : SemVer) = show x
42 |
43 | let integerParser = parse {
44 | let! digits = CharParsers.digit |> Primitives.many1
45 | let inline charToInt c = int c - int '0'
46 | return
47 | digits
48 | |> Seq.map charToInt
49 | |> Seq.fold (fun acc elem -> acc * 10 + elem) 0
50 | }
51 |
52 | let prefixParser = parse {
53 | do!
54 | CharParsers.asciiLetter
55 | |> Primitives.many1
56 | |>> ignore
57 | do! CharParsers.skipString "-"
58 | }
59 |
60 | let parser : Parser = parse {
61 | do! CharParsers.spaces
62 | do!
63 | (attempt prefixParser)
64 | <|> CharParsers.skipString "v"
65 | <|> CharParsers.skipString "V"
66 | |> Primitives.optional
67 |
68 | let! major = integerParser
69 | let segment = parse {
70 | do! CharParsers.skipString "."
71 | return! integerParser
72 | }
73 |
74 | let! minor = segment |> Primitives.opt
75 | let! patch = segment |> Primitives.opt
76 | let! increment = segment |> Primitives.opt
77 |
78 | return {
79 | Major = major;
80 | Minor = minor |> Option.defaultValue 0;
81 | Patch = patch |> Option.defaultValue 0;
82 | Increment = increment |> Option.defaultValue 0
83 | }
84 | }
85 |
86 | let private onlySemVerParser = parse {
87 | do! CharParsers.spaces
88 |
89 | let! semVer = parser
90 |
91 | do! CharParsers.spaces
92 | do! CharParsers.eof
93 |
94 | return semVer
95 | }
96 |
97 | let parse (x : string) : Result =
98 | match run onlySemVerParser x with
99 | | Success(result, _, _) -> Result.Ok result
100 | | Failure(errorMsg, _, _) -> Result.Error errorMsg
101 |
--------------------------------------------------------------------------------
/buckaroo/ShowCompletions.fs:
--------------------------------------------------------------------------------
1 | module Buckaroo.ShowCompletions
2 |
3 | open Buckaroo.Tasks
4 |
5 | let task context = async {
6 | let completions = [
7 | "init";
8 | "help";
9 | "install";
10 | "add";
11 | "resolve";
12 | "remove";
13 | "upgrade";
14 | "version";
15 | ]
16 |
17 | context.Console.Write(completions |> String.concat " ")
18 |
19 | return 0
20 | }
21 |
--------------------------------------------------------------------------------
/buckaroo/SourceExplorer.fs:
--------------------------------------------------------------------------------
1 | namespace Buckaroo
2 |
3 | open FSharp.Control
4 |
5 | type PackageSources = Map
6 |
7 | type VersionedLock = PackageLock * Set
8 |
9 | type ISourceExplorer =
10 | abstract member FetchVersions : PackageSources -> PackageIdentifier -> AsyncSeq
11 | abstract member FetchLocations : PackageSources -> PackageIdentifier -> Version -> AsyncSeq
12 | abstract member LockLocation : PackageLocation -> Async
13 | abstract member FetchManifest : PackageLock * Set -> Async
14 | abstract member FetchLock : PackageLock * Set -> Async
15 |
16 | type FetchResult =
17 | | Candidate of PackageLocation * Set
18 | | Unsatisfiable of Constraint
19 |
20 | module SourceExplorer =
21 |
22 | let private appendIfEmpty y (xs : AsyncSeq<_>) = asyncSeq {
23 | let mutable hasYielded = false
24 |
25 | for x in xs do
26 | yield x
27 | hasYielded <- true
28 |
29 | if not hasYielded
30 | then
31 | yield y
32 | }
33 |
34 | let private candidates xs =
35 | xs
36 | |> Seq.choose (fun x ->
37 | match x with
38 | | Candidate (location, versions) -> Some (location, versions)
39 | | _ -> None
40 | )
41 |
42 | let fetchLocationsForConstraint (sourceExplorer : ISourceExplorer) locations package versionConstraint =
43 | let rec loop (versionConstraint : Constraint) : AsyncSeq = asyncSeq {
44 | match Constraint.simplify versionConstraint with
45 | | Complement c ->
46 | let! complement =
47 | loop c
48 | |> AsyncSeq.choose (fun x ->
49 | match x with
50 | | Candidate (location, _) -> Some location
51 | | _ -> None
52 | )
53 | |> AsyncSeq.fold (fun s x -> Set.add x s) Set.empty
54 |
55 | yield!
56 | loop (Constraint.All Set.empty)
57 | |> AsyncSeq.filter (fun x ->
58 | match x with
59 | | Candidate (location, _) ->
60 | complement
61 | |> Set.contains location
62 | |> not
63 | | _ -> true
64 | )
65 | | Any xs ->
66 | yield!
67 | xs
68 | |> Set.toList
69 | |> List.sortDescending
70 | |> List.map loop
71 | |> AsyncSeq.mergeAll
72 | |> AsyncSeq.filter (fun x ->
73 | match x with
74 | | Candidate _ -> true
75 | | Unsatisfiable _ -> false
76 | )
77 | |> AsyncSeq.distinctUntilChanged
78 | |> appendIfEmpty (Unsatisfiable versionConstraint)
79 | | All xs ->
80 | let combine (xs : Set>) (ys : Set>) =
81 | xs
82 | |> Seq.choose (fun (location, versions) ->
83 | let matchingVersions =
84 | ys
85 | |> Seq.filter (fst >> (=) location)
86 | |> Seq.collect snd
87 | |> Seq.toList
88 |
89 | match matchingVersions with
90 | | [] -> None
91 | | vs -> Some (location, versions |> Set.union (set vs))
92 | )
93 | |> Set.ofSeq
94 |
95 | yield!
96 | (
97 | if Seq.isEmpty xs
98 | then
99 | sourceExplorer.FetchVersions locations package
100 | |> AsyncSeq.collect (fun version ->
101 | sourceExplorer.FetchLocations locations package version
102 | |> AsyncSeq.map (fun location -> Candidate (location, Set.singleton version))
103 | )
104 | else
105 | xs
106 | |> Set.toList
107 | |> List.sort
108 | |> List.map (loop >> (AsyncSeq.scan (fun s x -> Set.add x s) Set.empty))
109 | |> List.reduce (AsyncSeq.combineLatestWith (fun x y ->
110 | let maybeUnsatisfiable =
111 | x
112 | |> Set.union y
113 | |> Seq.tryFind (fun x ->
114 | match x with
115 | | Unsatisfiable _ -> true
116 | | _ -> false
117 | )
118 |
119 | match maybeUnsatisfiable with
120 | | Some unsat -> Set.singleton unsat
121 | | None ->
122 | let a = set (candidates x)
123 | let b = set (candidates y)
124 |
125 | combine a b |> Set.map Candidate
126 | ))
127 | |> AsyncSeq.concatSeq
128 | )
129 | |> AsyncSeq.distinctUntilChanged
130 | |> appendIfEmpty (Unsatisfiable versionConstraint)
131 |
132 | | Exactly version ->
133 | yield!
134 | sourceExplorer.FetchLocations locations package version
135 | |> AsyncSeq.map (fun location -> Candidate (location, Set.singleton version))
136 | |> appendIfEmpty (Unsatisfiable versionConstraint)
137 | | Range (op, v) ->
138 | yield!
139 | sourceExplorer.FetchVersions locations package
140 | |> AsyncSeq.choose (fun version ->
141 | match version with
142 | | SemVer v -> Some v
143 | | _ -> None)
144 | |> AsyncSeq.filter (Constraint.isWithinRange (op, v))
145 | |> AsyncSeq.map (Version.SemVer)
146 | |> AsyncSeq.collect (fun version ->
147 | sourceExplorer.FetchLocations locations package version
148 | |> AsyncSeq.map (fun l -> Candidate (l, Set.singleton version))
149 | )
150 | |> appendIfEmpty (Unsatisfiable versionConstraint)
151 | }
152 |
153 | loop versionConstraint
154 | |> AsyncSeq.takeWhileInclusive (fun x ->
155 | match x with
156 | | Candidate _ -> true
157 | | Unsatisfiable _ -> false
158 | )
--------------------------------------------------------------------------------
/buckaroo/StartCommand.fs:
--------------------------------------------------------------------------------
1 | module Buckaroo.StartCommand
2 |
3 | open System
4 | open Buckaroo.Tasks
5 | open Buckaroo.RichOutput
6 |
7 | let task (context : TaskContext) = async {
8 | let log (x : string) =
9 | let rt =
10 | x
11 | |> text
12 | |> foreground ConsoleColor.Green
13 | context.Console.Write rt
14 |
15 | let logUrl (x : string) =
16 | x
17 | |> text
18 | |> foreground ConsoleColor.Cyan
19 | |> background ConsoleColor.Black
20 | |> context.Console.Write
21 |
22 | log(" ___ __ ")
23 | log(" / _ )__ ______/ /_____ ________ ___ ")
24 | log(" / _ / // / __/ '_/ _ `/ __/ _ \\/ _ \\ ")
25 | log("/____/\\_,_/\\__/_/\\_\\\\_,_/_/ \\___/\\___/")
26 | log("")
27 | log("Buck, Buck, Buckaroo! \uD83E\uDD20")
28 |
29 | logUrl "https://buckaroo.pm"
30 |
31 | return 0
32 | }
33 |
--------------------------------------------------------------------------------
/buckaroo/Strings.fs:
--------------------------------------------------------------------------------
1 | module Buckaroo.Strings
2 |
3 | let replace (oldValue : string) (newValue : string) (target : string) =
4 | target.Replace (oldValue, newValue)
5 |
6 | let replaceAll (oldValues : string seq) (newValue : string) (target : string) =
7 | let rec loop oldValues target =
8 | match oldValues with
9 | | x::xs ->
10 | loop xs (replace x newValue target)
11 | | [] -> target
12 | loop (Seq.toList oldValues) target
13 |
14 | let substring startIndex (target : string) =
15 | target.Substring startIndex
16 |
17 | let truncate length (target : string) =
18 | target.Substring (0, length)
19 |
--------------------------------------------------------------------------------
/buckaroo/Target.fs:
--------------------------------------------------------------------------------
1 | namespace Buckaroo
2 |
3 | /// A target inside of a Buck cell
4 | /// For example:
5 | /// * "//:lib"
6 | /// * ":lib"
7 | /// * "//path/to/some:lib"
8 | /// * "//path/to/some"
9 | type Target = {
10 | Folders : string list;
11 | Name : string;
12 | }
13 |
14 | module Target =
15 |
16 | open FParsec
17 |
18 | let show (x : Target) : string =
19 | "//" + (x.Folders |> String.concat "/") + ":" + x.Name
20 |
21 | let private segmentParser = CharParsers.regex @"[a-zA-Z.\d](?:[a-zA-Z.\d]|_|\+|-(?=[a-zA-Z.\d])){0,38}"
22 |
23 | let private slash = CharParsers.skipString "/"
24 |
25 | let explicitNameParser = parse {
26 | let! folders = Primitives.sepEndBy segmentParser slash
27 | do! CharParsers.skipString ":"
28 | let! name = segmentParser
29 |
30 | return { Folders = folders; Name = name }
31 | }
32 |
33 | let implicitNameParser = parse {
34 | let slash = CharParsers.skipString "/"
35 | let! folders = Primitives.sepBy1 segmentParser slash
36 | do! slash |> Primitives.optional
37 | let name = folders |> List.rev |> List.head
38 |
39 | return { Folders = folders; Name = name }
40 | }
41 |
42 | let parser = parse {
43 | do! CharParsers.skipString "//" |> Primitives.optional
44 | return! Primitives.choice [ Primitives.attempt explicitNameParser; implicitNameParser ]
45 | }
46 |
47 | let parse (x : string) : Result =
48 | match run (parser .>> CharParsers.eof) x with
49 | | Success(result, _, _) -> Result.Ok result
50 | | Failure(error, _, _) -> Result.Error error
51 |
--------------------------------------------------------------------------------
/buckaroo/TargetIdentifier.fs:
--------------------------------------------------------------------------------
1 | namespace Buckaroo
2 |
3 | type TargetIdentifier = {
4 | PackagePath : PackageIdentifier list * PackageIdentifier
5 | Target : Target
6 | }
7 |
--------------------------------------------------------------------------------
/buckaroo/Tasks.fs:
--------------------------------------------------------------------------------
1 | module Buckaroo.Tasks
2 |
3 | open System
4 | open System.IO
5 | open System.Runtime
6 | open FSharpx
7 | open Buckaroo
8 | open Buckaroo.Console
9 | open Buckaroo.Logger
10 |
11 | type TaskContext = {
12 | Console : ConsoleManager
13 | DownloadManager : DownloadManager
14 | GitManager : GitManager
15 | SourceExplorer : ISourceExplorer
16 | BuildSystem : BuildSystem
17 | }
18 |
19 | let private isWindows () =
20 | let description =
21 | InteropServices.RuntimeInformation.OSDescription
22 | |> String.toLower
23 | (String.contains "win" description) && not (String.contains "darwin" description)
24 |
25 | let private determineBuildSystem (logger : Logger) = async {
26 | let useBazel =
27 | Environment.GetEnvironmentVariable "BUCKAROO_USE_BAZEL"
28 | |> String.IsNullOrWhiteSpace
29 | |> not
30 |
31 | if useBazel
32 | then
33 | return Bazel
34 | else
35 | let useBuck =
36 | Environment.GetEnvironmentVariable "BUCKAROO_USE_BUCK"
37 | |> String.IsNullOrWhiteSpace
38 | |> not
39 |
40 | if useBuck
41 | then
42 | return Buck
43 | else
44 | let! hasBuckConfig = Files.exists ".buckconfig"
45 |
46 | if hasBuckConfig
47 | then
48 | logger.Warning "Using the Buck build-system since a .buckconfig file was found. Set BUCKAROO_USE_BAZEL to override this or BUCKAROO_USE_BUCK to hide this warning. "
49 | return Buck
50 | else
51 | return Bazel
52 | }
53 |
54 | let private getCachePath = async {
55 | return
56 | match System.Environment.GetEnvironmentVariable("BUCKAROO_CACHE_PATH") with
57 | | null ->
58 | let personalDirectory =
59 | System.Environment.GetFolderPath Environment.SpecialFolder.Personal
60 | Path.Combine(personalDirectory, ".buckaroo", "cache")
61 | | path -> path
62 | }
63 |
64 | let getContext loggingLevel fetchStyle = async {
65 | let consoleManager = ConsoleManager loggingLevel
66 |
67 | let! cachePath = getCachePath
68 | let downloadManager = DownloadManager (consoleManager, cachePath)
69 |
70 | let! hasGit =
71 | Bash.runBashSync "git" "version" ignore ignore
72 | |> Async.Catch
73 | |> Async.map
74 | (Choice.toOption
75 | >> Option.map (fun _ -> true)
76 | >> Option.defaultValue false)
77 |
78 | let useLibGit2 =
79 | not (isNull (Environment.GetEnvironmentVariable "BUCKAROO_USE_LIBGIT2"))
80 | || not hasGit
81 |
82 | let git =
83 | if useLibGit2
84 | then GitLib(consoleManager) :> IGit
85 | else GitCli(consoleManager) :> IGit
86 |
87 | let gitManager = GitManager(fetchStyle, consoleManager, git, cachePath)
88 |
89 | let! buildSystem = determineBuildSystem (createLogger consoleManager None)
90 |
91 | let sourceExplorer = DefaultSourceExplorer(consoleManager, downloadManager, gitManager)
92 |
93 | return {
94 | Console = consoleManager
95 | DownloadManager = downloadManager
96 | GitManager = gitManager
97 | SourceExplorer = sourceExplorer
98 | BuildSystem = buildSystem
99 | }
100 | }
101 |
102 | let readManifest (root : string) = async {
103 | try
104 | let! content = Files.readFile (Path.Combine(root, Constants.ManifestFileName))
105 | return
106 | match Manifest.parse content with
107 | | Result.Ok manifest -> manifest
108 | | Result.Error error ->
109 | Exception ("Error parsing manifest:\n" + (Manifest.ManifestParseError.show error))
110 | |> raise
111 | with error ->
112 | return
113 | Exception ("Could not read project manifest. Are you sure you are in the right directory? ", error)
114 | |> raise
115 | }
116 |
117 | let writeManifest (manifest : Manifest) = async {
118 | let content = Manifest.toToml manifest
119 | return! Files.writeFile Constants.ManifestFileName content
120 | }
121 |
122 | let readLock = async {
123 | if File.Exists Constants.LockFileName
124 | then
125 | let! content = Files.readFile Constants.LockFileName
126 | return
127 | match Lock.parse content with
128 | | Result.Ok lock -> lock
129 | | Result.Error error ->
130 | Exception("Error reading lock file. " + error) |> raise
131 | else
132 | return Exception("No lock file was found. Perhaps you need to run 'buckaroo resolve'?") |> raise
133 | }
134 |
135 | let readLockIfPresent = async {
136 | if File.Exists(Constants.LockFileName)
137 | then
138 | let! lock = readLock
139 | return Some lock
140 | else
141 | return None
142 | }
143 |
144 | let writeLock (lock : Lock) = async {
145 | let content = Lock.toToml lock
146 | return! Files.writeFile Constants.LockFileName content
147 | }
148 |
--------------------------------------------------------------------------------
/buckaroo/Telemetry.fs:
--------------------------------------------------------------------------------
1 | module Buckaroo.Telemetry
2 |
3 | open System
4 | open System.IO
5 | open System.Runtime
6 | open FSharp.Data
7 | open FSharp.Data.HttpRequestHeaders
8 | open FSharpx
9 |
10 | let private isEnabled () =
11 | Environment.GetEnvironmentVariable "BUCKAROO_TELEMETRY_OPT_OUT" = null
12 |
13 | let private telemetryUrl = "https://analytics.buckaroo.pm"
14 |
15 | let private isUuid (x : string) =
16 | Guid.TryParse x |> fst
17 |
18 | let readOrGenerateUser = async {
19 | let p =
20 | Path.Combine(
21 | Environment.GetFolderPath Environment.SpecialFolder.Personal,
22 | ".buckaroo",
23 | "user.txt")
24 | try
25 | let! content = Files.readFile p
26 | let trimmed = content.Trim()
27 | if isUuid trimmed
28 | then
29 | return trimmed
30 | else
31 | File.Delete p
32 | let user = Guid.NewGuid() |> string
33 | do! Files.writeFile p user
34 | return user
35 | with _ ->
36 | let user = Guid.NewGuid() |> string
37 | do! Files.writeFile p user
38 | return user
39 | }
40 |
41 | let private getPlatform () =
42 | let parts =
43 | InteropServices.RuntimeInformation.OSDescription
44 | |> String.splitString [| "#" |] StringSplitOptions.RemoveEmptyEntries
45 | match parts with
46 | | [| x ; _ |] -> x
47 | | xs -> xs |> String.concat " "
48 |
49 | let postCommand session command = async {
50 | if not <| isEnabled ()
51 | then
52 | return ()
53 |
54 | let! user = readOrGenerateUser
55 |
56 | let json =
57 | [
58 | "{";
59 | " \"user\": \"" + user + "\", ";
60 | " \"session\": \"" + session + "\", ";
61 | " \"payload\": {";
62 | " \"command\": \"" + command + "\"";
63 | " }";
64 | "}";
65 | ]
66 | |> String.concat (System.Environment.NewLine)
67 |
68 | let userAgent =
69 | [
70 | "buckaroo";
71 | Constants.Version;
72 | getPlatform ();
73 | InteropServices.RuntimeInformation.OSArchitecture.ToString();
74 | ]
75 | |> String.concat " "
76 | |> String.splitString [| " " |] StringSplitOptions.RemoveEmptyEntries
77 | |> String.concat "-"
78 | |> String.toLower
79 |
80 | return!
81 | Http.AsyncRequest (
82 | telemetryUrl + "/logs",
83 | httpMethod = "POST",
84 | headers = [
85 | ContentType HttpContentTypes.Json;
86 | UserAgent userAgent;
87 | ],
88 | body = HttpRequestBody.TextRequest json
89 | )
90 | }
91 |
--------------------------------------------------------------------------------
/buckaroo/Toml.fs:
--------------------------------------------------------------------------------
1 | module Buckaroo.Toml
2 |
3 | open System
4 |
5 | type TomlError =
6 | | CouldNotParse of Exception
7 | | KeyNotFound of string
8 | | UnexpectedType of string
9 |
10 | module TomlError =
11 | let show (x : TomlError) =
12 | match x with
13 | | CouldNotParse e -> "Could not parse TOML " + (string e)
14 | | KeyNotFound k -> "Could not find an element with key " + k
15 | | UnexpectedType t -> "Unexpected type " + t
16 |
17 | let get (key : string) (table : Nett.TomlTable) =
18 | match table.TryGetValue key with
19 | | null -> TomlError.KeyNotFound key |> Result.Error
20 | | value -> Result.Ok value
21 |
22 | let tryGet (key : string) (table : Nett.TomlTable) =
23 | match table.TryGetValue key with
24 | | null -> None
25 | | value -> Some value
26 |
27 | let asArray (x : Nett.TomlObject) =
28 | try
29 | (x :?> Nett.TomlArray) |> Result.Ok
30 | with _ ->
31 | TomlError.UnexpectedType x.ReadableTypeName |> Result.Error
32 |
33 | let asString (x : Nett.TomlObject) =
34 | try
35 | (x :?> Nett.TomlString).Value |> Result.Ok
36 | with _ ->
37 | TomlError.UnexpectedType x.ReadableTypeName |> Result.Error
38 |
39 | let asBool (x : Nett.TomlObject) =
40 | try
41 | (x :?> Nett.TomlBool).Value |> Result.Ok
42 | with _ ->
43 | TomlError.UnexpectedType x.ReadableTypeName |> Result.Error
44 |
45 | let items (x : Nett.TomlArray) =
46 | x.Items |> Seq.toList
47 |
48 | let entries (x : Nett.TomlTable) =
49 | x.Keys
50 | |> Seq.map (fun k -> (k, x.Item k))
51 |
52 | let asTable (x : Nett.TomlObject) =
53 | try
54 | x :?> Nett.TomlTable |> Result.Ok
55 | with _ ->
56 | TomlError.UnexpectedType x.ReadableTypeName |> Result.Error
57 |
58 | let asTableArray (x : Nett.TomlObject) =
59 | try
60 | x :?> Nett.TomlTableArray |> Result.Ok
61 | with _ ->
62 | TomlError.UnexpectedType x.ReadableTypeName |> Result.Error
63 |
64 | let parse (x : string) =
65 | try
66 | let table = Nett.Toml.ReadString x
67 | table.Freeze () |> ignore
68 | Result.Ok table
69 | with error ->
70 | TomlError.CouldNotParse error |> Result.Error
71 |
72 | let compareFieldIn toml field x =
73 | toml
74 | |> Result.bind (get field)
75 | |> Result.bind asString
76 | |> Result.map (fun y -> x = y)
77 |
78 | let rec compareTable table fields =
79 | match fields with
80 | | (key, value)::xs ->
81 | match (compareFieldIn table key value) with
82 | | Result.Ok true -> compareTable table xs
83 | | _ -> false
84 | | [] -> true
85 |
86 | let formatDateTime (x : System.DateTime) =
87 | x.ToString("yyyy-MM-dd'T'HH:mm:ss")
88 |
--------------------------------------------------------------------------------
/buckaroo/UpgradeCommand.fs:
--------------------------------------------------------------------------------
1 | module Buckaroo.UpgradeCommand
2 |
3 | open System
4 | open System.IO
5 | open Buckaroo
6 | open Buckaroo.Tasks
7 | open Buckaroo.RichOutput
8 | open Buckaroo.Logger
9 | open Buckaroo
10 |
11 | let task context (packages : List) = async {
12 | let logger = createLogger context.Console None
13 |
14 | if Seq.isEmpty packages
15 | then
16 | logger.Info "Upgrading all packages... "
17 | else
18 | logger.Info
19 | <| "Upgrading [ " + (packages |> Seq.map PackageIdentifier.show |> String.concat " ") + " ]... "
20 |
21 | let extractPartialSolution =
22 | async {
23 | if File.Exists (Constants.LockFileName)
24 | then
25 | let! lock = Tasks.readLock
26 | let! partial =
27 | if packages |> Seq.isEmpty
28 | then
29 | async { return Solution.empty }
30 | else
31 | async {
32 | let! solution = Solver.fromLock context.SourceExplorer lock
33 |
34 | return solution
35 | }
36 |
37 | return partial
38 | else
39 | logger.Warning
40 | "There is no lock-file to upgrade. A fresh lock-file will be generated. "
41 |
42 | return Solution.empty
43 | }
44 |
45 | let! partial = extractPartialSolution
46 |
47 | let! resolveSucceeded =
48 | ResolveCommand.task context partial ResolutionStyle.Upgrading
49 |
50 | if resolveSucceeded
51 | then
52 | let! install = InstallCommand.task context
53 |
54 | if install <> 0
55 | then
56 | logger.Error "Upgraded version(s) were found but the packages could not be installed. "
57 | return 1
58 | else
59 | logger.Success "The upgrade is complete. "
60 | return 0
61 | else
62 | logger.Error "The upgrade failed. No packages were changed. "
63 | return 1
64 | }
65 |
--------------------------------------------------------------------------------
/buckaroo/Version.fs:
--------------------------------------------------------------------------------
1 | namespace Buckaroo
2 |
3 | open Buckaroo.Git
4 | open Buckaroo.RichOutput
5 |
6 | type GitVersion =
7 | | Revision of Revision
8 | | Branch of Branch
9 | | Tag of Tag
10 |
11 | type Version =
12 | | Git of GitVersion
13 | | SemVer of SemVer
14 |
15 | module Version =
16 |
17 | open FParsec
18 |
19 | // score returns ranks version by the likelyhood of change.
20 | let private score v =
21 | match v with
22 | | Git (Revision _) -> 0
23 | | Git (Tag _) -> 1
24 | | SemVer _ -> 2
25 | | Git (Branch "master") -> 4
26 | | Git (Branch "develop") -> 5
27 | | Git (Branch _) -> 3
28 |
29 | let compare (x : Version) (y : Version) =
30 | match (score x - score y, x, y) with
31 | | (0, Version.SemVer i, Version.SemVer j) -> SemVer.compare i j
32 | | (0, Git(Tag i), Git(Tag j)) -> System.String.Compare(i, j)
33 | | (0, Git(Branch i), Git(Branch j)) -> System.String.Compare(i, j)
34 | | (0, Git(Revision i), Git(Revision j)) -> System.String.Compare(i, j)
35 | | (c, _, _) -> c
36 |
37 | let rec show (v : Version) : string =
38 | match v with
39 | | SemVer semVer -> SemVer.show semVer
40 | | Git (Branch branch) -> "branch=" + branch
41 | | Git (Revision revision) -> "revision=" + revision
42 | | Git (Tag tag) -> "tag=" + tag
43 |
44 | let rec showRich (v : Version) : RichOutput =
45 | match v with
46 | | SemVer semVer -> semVer |> string |> highlight
47 | | Git (Branch branch) -> (highlight "branch") + (subtle "=") + (highlight branch)
48 | | Git (Revision revision) -> (highlight "revision") + (subtle "=") + (highlight revision)
49 | | Git (Tag tag) -> (highlight "tag") + (subtle "=") + (highlight tag)
50 |
51 | let identifierParser = CharParsers.regex @"[a-zA-Z\d](?:[a-zA-Z\d]|-(?=[a-zA-Z\d])){2,64}"
52 |
53 | let tagVersionParser = parse {
54 | do! CharParsers.skipString "tag="
55 | let! tag = Git.branchOrTagNameParser
56 | return Version.Git (GitVersion.Tag tag)
57 | }
58 |
59 | let branchVersionParser = parse {
60 | do! CharParsers.skipString "branch="
61 | let! branch = Git.branchOrTagNameParser
62 | return Version.Git (GitVersion.Branch branch)
63 | }
64 |
65 | let revisionVersionParser = parse {
66 | do! CharParsers.skipString "revision="
67 | let! revision = identifierParser
68 | return Version.Git (GitVersion.Revision revision)
69 | }
70 |
71 | let semVerVersionParser = parse {
72 | let! semVer = SemVer.parser
73 | return Version.SemVer semVer
74 | }
75 |
76 | let parser =
77 | tagVersionParser
78 | <|> branchVersionParser
79 | <|> revisionVersionParser
80 | <|> semVerVersionParser
81 |
82 | let parse (x : string) : Result =
83 | match run parser x with
84 | | Success(result, _, _) -> Result.Ok result
85 | | Failure(errorMsg, _, _) -> Result.Error errorMsg
86 |
87 |
88 | let showSet (x : Set) =
89 | x
90 | |> Set.toSeq
91 | |> Seq.map show
92 | |> String.concat ", "
93 | |> (fun x -> "{" + x + "}")
94 |
95 | let showRichSet (x : Set) =
96 | x
97 | |> Set.toSeq
98 | |> Seq.map showRich
99 | |> Seq.reduce (fun x y -> x + (subtle ", ") + y)
100 | |> (fun x ->
101 | (subtle "{") + x +
102 | (subtle "}") )
--------------------------------------------------------------------------------
/buckaroo/VersionCommand.fs:
--------------------------------------------------------------------------------
1 | module Buckaroo.VersionCommand
2 |
3 | let task _ = async {
4 | System.Console.WriteLine (Constants.Version)
5 |
6 | return 0
7 | }
8 |
--------------------------------------------------------------------------------
/buckaroo/buckaroo.fsproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net6.0
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
--------------------------------------------------------------------------------
/buckaroo_bash_completion.bash:
--------------------------------------------------------------------------------
1 | # Loosely based on code by eliben
2 | # https://eli.thegreenplace.net/2013/12/26/adding-bash-completion-for-your-own-tools-an-example-for-pss
3 |
4 | _buckaroo_complete()
5 | {
6 | local cur_word prev_word type_list
7 |
8 | # COMP_WORDS is an array of words in the current command line.
9 | # COMP_CWORD is the index of the current word (the one the cursor is
10 | # in). So COMP_WORDS[COMP_CWORD] is the current word; we also record
11 | # the previous word here, although this specific script doesn't
12 | # use it yet.
13 | cur_word="${COMP_WORDS[COMP_CWORD]}"
14 | prev_word="${COMP_WORDS[COMP_CWORD-1]}"
15 |
16 | # Ask pss to generate a list of types it supports
17 | # TODO: Fetch commands dynamically from Buckaroo
18 | commands='init install resolve remove add upgrade version help'
19 | # commands=`buckaroo show-completions`
20 |
21 | # COMPREPLY is the array of possible completions, generated with
22 | # the compgen builtin.
23 | COMPREPLY=( $(compgen -W "${commands}" -- ${cur_word}) )
24 |
25 | return 0
26 | }
27 |
28 | # Register _buckaroo_complete to provide completion for the following commands
29 | complete -F _buckaroo_complete buckaroo
30 |
--------------------------------------------------------------------------------
/warp-bundle-linux.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | export CppCompilerAndLinker=clang
4 |
5 | wget -c -O warp-packer https://github.com/dgiagio/warp/releases/download/v0.3.0/linux-x64.warp-packer
6 |
7 | chmod +x ./warp-packer
8 | ./warp-packer --version
9 |
10 | dotnet publish ./buckaroo-cli/ -c Release -r linux-x64
11 |
12 | mkdir -p warp
13 | rm -rf ./warp/buckaroo-linux
14 |
15 | ./warp-packer --arch linux-x64 --exec buckaroo-cli --input_dir ./buckaroo-cli/bin/Release/netcoreapp2.1/linux-x64 --output warp/buckaroo-linux
16 | ./warp/buckaroo-linux
17 |
--------------------------------------------------------------------------------
/warp-bundle-macos.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | wget -O warp-packer https://github.com/dgiagio/warp/releases/download/v0.3.0/macos-x64.warp-packer
4 |
5 | chmod +x ./warp-packer
6 | ./warp-packer --version
7 |
8 | dotnet publish ./buckaroo-cli/ -c Release -r osx-x64
9 |
10 | mkdir -p warp
11 | rm -rf ./warp/buckaroo-macos
12 |
13 | ./warp-packer --arch macos-x64 --exec buckaroo-cli --input_dir ./buckaroo-cli/bin/Release/netcoreapp2.1/osx-x64 --output warp/buckaroo-macos
14 | ./warp/buckaroo-macos
15 |
--------------------------------------------------------------------------------
/warp-bundle-windows.ps1:
--------------------------------------------------------------------------------
1 | wget https://github.com/dgiagio/warp/releases/download/v0.3.0/windows-x64.warp-packer.exe -OutFile warp-packer.exe
2 |
3 | dotnet publish ./buckaroo-cli/ -c Release -r win10-x64
4 |
5 | md .\warp -ea 0
6 |
7 | .\warp-packer.exe --arch windows-x64 --input_dir .\buckaroo-cli\bin\Release\netcoreapp2.1\win10-x64 --exec buckaroo-cli.exe --output .\warp\buckaroo-windows.exe
8 |
9 | .\warp\buckaroo-windows.exe
10 |
--------------------------------------------------------------------------------
/www/git.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LoopPerfect/buckaroo/5f94e23b7a05ae4b3ddf477105214546e8b4b25e/www/git.png
--------------------------------------------------------------------------------
/www/how-buckaroo-works.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LoopPerfect/buckaroo/5f94e23b7a05ae4b3ddf477105214546e8b4b25e/www/how-buckaroo-works.png
--------------------------------------------------------------------------------
/www/ides.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LoopPerfect/buckaroo/5f94e23b7a05ae4b3ddf477105214546e8b4b25e/www/ides.png
--------------------------------------------------------------------------------
/www/logo-medium.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LoopPerfect/buckaroo/5f94e23b7a05ae4b3ddf477105214546e8b4b25e/www/logo-medium.png
--------------------------------------------------------------------------------
/www/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LoopPerfect/buckaroo/5f94e23b7a05ae4b3ddf477105214546e8b4b25e/www/logo.png
--------------------------------------------------------------------------------
/www/packages.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LoopPerfect/buckaroo/5f94e23b7a05ae4b3ddf477105214546e8b4b25e/www/packages.png
--------------------------------------------------------------------------------
/www/registries.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LoopPerfect/buckaroo/5f94e23b7a05ae4b3ddf477105214546e8b4b25e/www/registries.png
--------------------------------------------------------------------------------
/www/usual-suspects.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LoopPerfect/buckaroo/5f94e23b7a05ae4b3ddf477105214546e8b4b25e/www/usual-suspects.png
--------------------------------------------------------------------------------