├── .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 | Buckaroo 3 |

4 | 5 | # Buckaroo 6 | 7 | The decentralized package manager for C++ and friends. 8 | 9 | [![](https://img.shields.io/travis/LoopPerfect/buckaroo/buckaroo-redux.svg)](https://travis-ci.org/LoopPerfect/buckaroo) [![](https://img.shields.io/appveyor/ci/njlr/buckaroo/buckaroo-redux.svg)](https://ci.appveyor.com/project/njlr/buckaroo) 10 | [![](https://img.shields.io/badge/docs-wiki-blue.svg)](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 | Package Registries 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 | IDE Integrations 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 | Buckaroo 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 --------------------------------------------------------------------------------