├── .gitattributes ├── .github ├── dco.yml └── workflows │ ├── dkml-release.yml │ ├── pages.yml │ ├── syntax.yml │ └── test.yml ├── .gitignore ├── .ocamlformat ├── CHANGES.md ├── CONTRIBUTORS.md ├── COPYRIGHT ├── DEVELOPING.md ├── LICENSE ├── README.md ├── ci ├── build-test.sh ├── crosscompiling-opam-installer.sh ├── crosscompiling-workspace-generator.sh ├── prepare-release.sh └── setup-dkml │ ├── gh-darwin │ ├── post │ │ ├── action.yml │ │ └── dune │ └── pre │ │ ├── action.yml │ │ └── dune │ ├── gh-linux │ ├── post │ │ ├── action.yml │ │ └── dune │ └── pre │ │ ├── action.yml │ │ └── dune │ ├── gh-windows │ ├── post │ │ ├── action.yml │ │ └── dune │ └── pre │ │ ├── action.yml │ │ └── dune │ ├── gl │ ├── dune │ └── setup-dkml.gitlab-ci.yml │ └── pc │ ├── dune │ ├── setup-dkml-darwin_x86_64.sh │ ├── setup-dkml-linux_x86.sh │ ├── setup-dkml-linux_x86_64.sh │ ├── setup-dkml-windows_x86.ps1 │ └── setup-dkml-windows_x86_64.ps1 ├── cmake └── FindDkToolScripts.cmake ├── diskuvbox-maintain.opam ├── diskuvbox.opam ├── diskuvbox.opam.template ├── dk ├── dk.cmd ├── dune ├── dune-project ├── etc ├── headache.conf └── license-header.txt ├── mdx-console.sh ├── src ├── bin │ ├── diskuvbox.dlls.txt │ ├── dune │ ├── log_config.ml │ ├── main.ml │ └── tests │ │ ├── copy-dir.t │ │ ├── copy-file-into.t │ │ ├── copy-file-into_prefix.t │ │ ├── copy-file.t │ │ ├── dune │ │ ├── find-up.t │ │ ├── touch-file.t │ │ ├── tree-README-example.t │ │ └── tree.t └── lib │ ├── diskuvbox.ml │ ├── diskuvbox.mli │ ├── dune │ └── dune.runlicense.inc └── support ├── .gitignore ├── test-32bit-helper.sh ├── test-32bit.sh └── test_32bit_bos.ml /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set the default behavior, in case people don't have core.autocrlf set. 2 | # This is critical for Windows and UNIX interoperability. 3 | * text=auto 4 | 5 | # Explicitly declare text files you want to always be normalized and converted 6 | # to native line endings on checkout. 7 | *.c text 8 | *.h text 9 | *.md text 10 | *.opam text 11 | *.opam.template text 12 | Makefile text 13 | dune text 14 | dune-project text 15 | license-header.txt text 16 | opam text 17 | 18 | # Declare files that will always have LF line endings on checkout. 19 | *.patch text eol=lf 20 | *.sexp text eol=lf 21 | *.sh text eol=lf 22 | *.mli text eol=lf linguist-language=OCaml 23 | *.ml text eol=lf linguist-language=OCaml 24 | *.md text eol=lf 25 | *.opam text eol=lf 26 | *.opam.template text eol=lf 27 | license-header.txt text eol=lf 28 | 29 | # Declare files that will always have CRLF line endings on checkout. 30 | *.sln text eol=crlf 31 | 32 | # https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_character_encoding?view=powershell-7.1 33 | # > Creating PowerShell scripts on a Unix-like platform or using a cross-platform editor on Windows, such as Visual Studio Code, 34 | # > results in a file encoded using UTF8NoBOM. These files work fine on PowerShell Core, but may break in Windows PowerShell if 35 | # > the file contains non-Ascii characters. 36 | # > In general, Windows PowerShell uses the Unicode UTF-16LE encoding by default. 37 | # > Using any Unicode encoding, except UTF7, always creates a BOM. 38 | # 39 | # Hint: If a file is causing you problems (ex. `fatal: BOM is required in ... if encoded as UTF-16`) use 40 | # "View > Change File Encoding > Save with Encoding > UTF-16LE" in Visual Studio Code to save the file correctly. 41 | *.ps1 text working-tree-encoding=UTF-16 eol=crlf 42 | *.psm1 text working-tree-encoding=UTF-16 eol=crlf 43 | -------------------------------------------------------------------------------- /.github/dco.yml: -------------------------------------------------------------------------------- 1 | allowRemediationCommits: 2 | individual: true 3 | require: 4 | members: false 5 | -------------------------------------------------------------------------------- /.github/workflows/dkml-release.yml: -------------------------------------------------------------------------------- 1 | ########################################################################## 2 | # File: .github/workflows/dkml-release.yml # 3 | # # 4 | # Copyright 2022 Diskuv, Inc. # 5 | # # 6 | # Licensed under the Open Software License version 3.0 # 7 | # (the "License"); you may not use this file except in compliance # 8 | # with the License. You may obtain a copy of the License at # 9 | # # 10 | # https://opensource.org/license/osl-3-0-php/ # 11 | # # 12 | ########################################################################## 13 | 14 | # Maintainer note: This .yml file is used as the simple example of dkml-workflows. Keep it simple and documented! 15 | 16 | name: Create diskuvbox releases 17 | 18 | env: 19 | OPAM_PACKAGE: "diskuvbox" 20 | EXECUTABLE_NAME: "diskuvbox" 21 | DKML_COMPILER: "" # You can override the dkml-compiler package version. Example: 2.0.2 22 | CACHE_PREFIX: "v1" 23 | # Secondary switch 'two' used to install opam-installer for Dune-ified crosscompiling builds 24 | SECONDARY_SWITCH: "true" 25 | 26 | # Trigger whenever there is a git push to main/master branch, or whenever a version tag is pushed 27 | on: 28 | push: 29 | branches: 30 | - "main" 31 | tags: 32 | - "[0-9]+.*" 33 | - "v*" 34 | # ... or trigger manually from GitHub web interface 35 | workflow_dispatch: 36 | # ... or prime the caches every Wednesday at 18:00 UTC 37 | schedule: 38 | - cron: 0 18 * * WED 39 | jobs: 40 | build: 41 | strategy: 42 | fail-fast: false 43 | matrix: 44 | include: 45 | - gh_os: windows-2019 46 | abi_pattern: win32-windows_x86 47 | dkml_host_abi: windows_x86 48 | - gh_os: windows-2019 49 | abi_pattern: win32-windows_x86_64 50 | dkml_host_abi: windows_x86_64 51 | - gh_os: ubuntu-latest 52 | abi_pattern: manylinux2014-linux_x86 53 | dkml_host_abi: linux_x86 54 | - gh_os: ubuntu-latest 55 | abi_pattern: manylinux2014-linux_x86_64 56 | dkml_host_abi: linux_x86_64 57 | - gh_os: macos-latest 58 | abi_pattern: macos-darwin_all 59 | dkml_host_abi: darwin_x86_64 60 | 61 | runs-on: ${{ matrix.gh_os }} 62 | name: build / ${{ matrix.abi_pattern }} 63 | 64 | steps: 65 | - name: Checkout code 66 | uses: actions/checkout@v3 67 | 68 | - name: Cache cross-compilation tools 69 | uses: actions/cache@v3 70 | with: 71 | path: .ci/cross 72 | key: 73 | "${{ runner.os }}-cross-${{ hashFiles('ci/*.sh') }}" 74 | 75 | - name: Cache DkML compilers code 76 | uses: actions/cache@v3 77 | id: cache-dkml-compilers 78 | with: 79 | path: .ci/dkml-compilers 80 | key: ${{ runner.os }} 81 | 82 | - name: Checkout DkML compilers code 83 | if: steps.cache-dkml-compilers.outputs.cache-hit != 'true' 84 | # For help: ./dk dkml.workflow.compilers HELP 85 | run: ./dk dkml.workflow.compilers PRERELEASE CI GitHub 86 | 87 | # The .ci/dkml-compilers "pre" actions will create the environment variables: 88 | # opam_root, exe_ext, dkml_host_abi, abi_pattern (and many more) 89 | # 90 | - name: Setup DkML compilers on a Windows host 91 | if: startsWith(matrix.dkml_host_abi, 'windows_') 92 | uses: ./.ci/dkml-compilers/gh-windows/pre 93 | with: 94 | DKML_COMPILER: ${{ env.DKML_COMPILER }} 95 | CACHE_PREFIX: ${{ env.CACHE_PREFIX }} 96 | SECONDARY_SWITCH: ${{ env.SECONDARY_SWITCH }} 97 | - name: Setup DkML compilers on a Linux host 98 | if: startsWith(matrix.dkml_host_abi, 'linux_') 99 | uses: ./.ci/dkml-compilers/gh-linux/pre 100 | with: 101 | DKML_COMPILER: ${{ env.DKML_COMPILER }} 102 | CACHE_PREFIX: ${{ env.CACHE_PREFIX }} 103 | SECONDARY_SWITCH: ${{ env.SECONDARY_SWITCH }} 104 | - name: Setup DkML compilers on a Darwin host 105 | if: startsWith(matrix.dkml_host_abi, 'darwin_') 106 | uses: ./.ci/dkml-compilers/gh-darwin/pre 107 | with: 108 | DKML_COMPILER: ${{ env.DKML_COMPILER }} 109 | CACHE_PREFIX: ${{ env.CACHE_PREFIX }} 110 | SECONDARY_SWITCH: ${{ env.SECONDARY_SWITCH }} 111 | 112 | # This section is for your own build logic which you should place in 113 | # ci/build-test.sh or a similar file 114 | 115 | - name: Build and test the package on Windows host 116 | if: startsWith(matrix.dkml_host_abi, 'windows_') 117 | shell: msys2 {0} 118 | run: ci/build-test.sh --opam-package ${{ env.OPAM_PACKAGE }} --executable-name ${{ env.EXECUTABLE_NAME }} 119 | 120 | - name: Build and test the package on non-Windows host 121 | if: "!startsWith(matrix.dkml_host_abi, 'windows_')" 122 | run: ci/build-test.sh --opam-package ${{ env.OPAM_PACKAGE }} --executable-name ${{ env.EXECUTABLE_NAME }} 123 | 124 | # The .ci/dkml-compilers "post" actions will finalize caching, etc. 125 | 126 | - name: Teardown DkML compilers on a Windows host 127 | if: startsWith(matrix.dkml_host_abi, 'windows_') 128 | uses: ./.ci/dkml-compilers/gh-windows/post 129 | 130 | - name: Teardown DkML compilers on a Darwin host 131 | if: startsWith(matrix.dkml_host_abi, 'darwin_') 132 | uses: ./.ci/dkml-compilers/gh-darwin/post 133 | 134 | - name: Teardown DkML compilers on a Linux host 135 | if: startsWith(matrix.dkml_host_abi, 'linux_') 136 | uses: ./.ci/dkml-compilers/gh-linux/post 137 | 138 | # Upload artifact 139 | 140 | - uses: actions/upload-artifact@v3 141 | with: 142 | name: ${{ matrix.dkml_host_abi }} 143 | path: dist/ 144 | 145 | #-------- 146 | # Release 147 | #-------- 148 | 149 | release: 150 | runs-on: ubuntu-latest 151 | permissions: 152 | contents: write # Needed for softprops/action-gh-release@v1 153 | # Wait until `build` complete 154 | needs: 155 | - build 156 | steps: 157 | - name: Checkout code 158 | uses: actions/checkout@v3 159 | 160 | - uses: actions/download-artifact@v3 161 | with: 162 | path: dist 163 | 164 | - name: Restructure multi-ABI directories 165 | run: ci/prepare-release.sh 166 | 167 | - name: Release (only when Git tag pushed) 168 | uses: softprops/action-gh-release@v1 169 | if: startsWith(github.ref, 'refs/tags/') 170 | with: 171 | files: | 172 | _release/* 173 | -------------------------------------------------------------------------------- /.github/workflows/pages.yml: -------------------------------------------------------------------------------- 1 | name: Publish GitHub Pages 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'main' 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | permissions: 12 | # Needed for peaceiris/actions-gh-pages@v3 13 | contents: write 14 | steps: 15 | - name: Checkout code 16 | uses: actions/checkout@v3 17 | 18 | - name: OCaml 4.13.x 19 | uses: ocaml/setup-ocaml@v2 20 | with: 21 | ocaml-compiler: 4.13.x 22 | dune-cache: false # true does not publish pages consistently 23 | - name: Install Opam dependencies 24 | run: opam install . --deps-only --with-doc 25 | - name: Build odoc 26 | run: opam exec -- dune build @doc 27 | - name: Deploy to GitHub Pages 28 | uses: peaceiris/actions-gh-pages@v3 29 | with: 30 | github_token: ${{ secrets.GITHUB_TOKEN }} 31 | publish_dir: _build/default/_doc/_html 32 | -------------------------------------------------------------------------------- /.github/workflows/syntax.yml: -------------------------------------------------------------------------------- 1 | name: Syntax Check 2 | 3 | on: push 4 | 5 | jobs: 6 | syntax-check: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@master 10 | - name: Check Markdown links 11 | uses: gaurav-nelson/github-action-markdown-link-check@v1 12 | with: 13 | use-verbose-mode: 'yes' 14 | - name: Run ShellCheck 15 | uses: ludeeus/action-shellcheck@master 16 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Box Tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'main' 7 | tags: 8 | - "[0-9]+.*" 9 | - "v*" 10 | # ... or trigger manually from GitHub web interface 11 | workflow_dispatch: 12 | # ... or prime the caches every Wednesday at 18:00 UTC 13 | schedule: 14 | - cron: 0 18 * * WED 15 | 16 | jobs: 17 | build: 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | os: 22 | - windows-latest 23 | - ubuntu-latest 24 | - macos-latest 25 | ocaml-compiler: 26 | # --------------- 27 | # Why not 4.08.0? 28 | # --------------- 29 | # 30 | # It fails on macos: https://github.com/diskuv/diskuvbox/runs/5891371632?check_suite_focus=true 31 | # 32 | #=== ERROR while compiling ppx_deriving.5.2.1 =================================# 33 | # context 2.1.2 | macos/x86_64 | ocaml-base-compiler.4.08.0 | git+https://github.com/ocaml/opam-repository.git 34 | # path ~/work/diskuvbox/diskuvbox/_opam/.opam-switch/build/ppx_deriving.5.2.1 35 | # command ~/.opam/opam-init/hooks/sandbox.sh build dune build -p ppx_deriving -j 3 36 | # exit-code 1 37 | # env-file ~/.opam/log/ppx_deriving-6411-7f9caa.env 38 | # output-file ~/.opam/log/ppx_deriving-6411-7f9caa.out 39 | ### output ### 40 | # File "src_plugins/create/dune", line 11, characters 2-23: 41 | # 11 | (pps ppxlib.metaquot)) 42 | # ^^^^^^^^^^^^^^^^^^^^^ 43 | # (cd _build/default && .ppx/0224ad3443a846e54f1637fccb074e7d/ppx.exe --cookie 'library-name="ppx_deriving_create"' -o src_plugins/create/ppx_deriving_create.pp.ml --impl src_plugins/create/ppx_deriving_create.ml -corrected-suffix .ppx-corrected -diff-cmd - -dump-ast) 44 | # Command got signal KILL. 45 | # 46 | # --------------- 47 | # Why not 4.09.0? 48 | # --------------- 49 | # 50 | # It fails on macos: https://github.com/diskuv/diskuvbox/actions/runs/3332746014/jobs/5514449266 51 | # 52 | #=== ERROR while compiling base.v0.14.3 =======================================# 53 | # context 2.1.3 | macos/x86_64 | ocaml-base-compiler.4.09.0 | git+https://github.com/ocaml/opam-repository.git 54 | # path ~/work/diskuvbox/diskuvbox/_opam/.opam-switch/build/base.v0.14.3 55 | # command ~/.opam/opam-init/hooks/sandbox.sh build dune build -p base -j 3 56 | # exit-code 1 57 | # env-file ~/.opam/log/base-15853-2d07a0.env 58 | # output-file ~/.opam/log/base-15853-2d07a0.out 59 | ### output ### 60 | # File "compiler-stdlib/src/dune", line 3, characters 0-97: 61 | # 3 | (rule (targets caml.ml) 62 | # 4 | (action (run ../gen/gen.exe -ocaml-where %{ocaml_where} -o %{targets}))) 63 | # (cd _build/default/compiler-stdlib/src && ../gen/gen.exe -ocaml-where /Users/runner/work/diskuvbox/diskuvbox/_opam/lib/ocaml -o caml.ml) 64 | # Command got signal KILL. 65 | - '4.10.0' 66 | - '4.13.x' 67 | runs-on: ${{ matrix.os }} 68 | name: ${{ matrix.os }}-${{ matrix.ocaml-compiler }} 69 | steps: 70 | - name: Checkout code 71 | uses: actions/checkout@v3 72 | 73 | - name: OCaml ${{ matrix.ocaml-compiler }} with Dune cache 74 | uses: ocaml/setup-ocaml@v2 75 | if: ${{ !startsWith(matrix.os, 'windows-') }} 76 | with: 77 | ocaml-compiler: ${{ matrix.ocaml-compiler }} 78 | dune-cache: true 79 | - name: OCaml ${{ matrix.ocaml-compiler }} without Dune cache 80 | uses: ocaml/setup-ocaml@v2 81 | if: ${{ startsWith(matrix.os, 'windows-') }} 82 | with: 83 | ocaml-compiler: ${{ matrix.ocaml-compiler }} 84 | dune-cache: false 85 | cache-prefix: v2 86 | - name: Install Opam dependencies 87 | run: opam install . --deps-only --with-test 88 | - name: Build OCaml 89 | run: opam exec -- dune build --display=short 90 | - name: Test OCaml excluding timestamp tests 91 | if: ${{ !startsWith(matrix.os, 'ubuntu-') }} 92 | run: opam exec -- dune runtest --display=short 93 | - name: Test OCaml including timestamp tests 94 | if: ${{ startsWith(matrix.os, 'ubuntu-') }} 95 | run: opam exec -- env BOX_TIMESTAMP_TESTS=true dune runtest --display=short 96 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Opam 2 | _opam/ 3 | 4 | # Visual Studio Code 5 | .vscode/settings.json 6 | 7 | # Dune 8 | _build/ 9 | /*.install 10 | 11 | # GitHub Actions 12 | /download-cache/ 13 | 14 | # direnv (https://direnv.net/) 15 | .envrc 16 | 17 | # CI 18 | .ci/ 19 | dist/ 20 | -------------------------------------------------------------------------------- /.ocamlformat: -------------------------------------------------------------------------------- 1 | version=0.24.1 2 | profile=conventional 3 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | # Changes 2 | 3 | ## 0.2.0 4 | 5 | - Switch to `cmdliner >= 1.1.0`. Require `result >= 1.5` 6 | - Fix bug where `copy_file` would fail on Windows if the file exists and was 7 | readonly 8 | - Add `--prefix` and `--suffix` options for `diskuvbox copy-file-into`, 9 | `diskuvbox copy-file` and `diskuvbox copy-dir` 10 | 11 | ## 0.1.2 12 | 13 | - Reduce the number of opam dependencies by splitting diskuvbox.opam from 14 | diskuvbox-maintain.opam 15 | 16 | ## 0.1.1 17 | 18 | - Use memory buffering to copy files. Removes 16 MiB max file limitation on 19 | 32-bit OCaml. 20 | - Validate and document a bytecode guarantee that only standard stublibs are used 21 | - Distribute binaries with setup-dkml.yml@v1 22 | - Fix Dune build steps so works under cross-compiler 23 | - Code working with Cmdliner.1.1.1 24 | - Increase minimum OCaml to 4.10 to work on macOS 25 | - Cross-compile `darwin_arm64` on `darwin_x86_64` 26 | 27 | ## 0.1.0 28 | 29 | - Initial release 30 | - Error when copy_file SRCFILE is not an existing file 31 | - Error when copy_dir SRCDIR is not an existing directory 32 | - Error when walk_down FROMPATH is not an existing path 33 | - Fix find-up validation removing search names 34 | - Avoid PATH shadowing tests 35 | -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | # Your Contributions 2 | 3 | Diskuv Box accepts Pull Requests (PRs)! 4 | 5 | Before you start writing a PR, please be aware of three things: 6 | 1. The project code is under the Apache v2.0 license. People *will* be able 7 | to use your contributions for commercial code! 8 | 2. We only accept PRs that have signed the [Developer Certificate of Origin (DCO)](https://developercertificate.org/) 9 | license. You sign by including a `Signed-off-by` line 10 | with an email address that matches the commit author. For example, your 11 | commit message could look like: 12 | 13 | ``` 14 | This is my commit message 15 | 16 | Signed-off-by: Random J Developer 17 | ``` 18 | 19 | or you can just use `git commit -s -m 'This is my commit message'`. 20 | 3. Especially if this is your first PR, it is helpful to open an issue first 21 | so your upcoming contribution idea can be sanity tested. 22 | 23 | If you would like to develop a new Box command, you will need to: 24 | 25 | * Add a function to the library at [src/lib/diskuvbox.mli](src/lib/diskuvbox.mli) 26 | and [src/lib/diskuvbox.ml](src/lib/diskuvbox.ml). Each command usually 27 | gets its own library function, but there are exceptions like 28 | the [copy-file](README.md#diskuvbox-copy-file) and [copy-file-into](README.md#diskuvbox-copy-file) commands that both use the same 29 | [copy_file](https://diskuv.github.io/diskuvbox/diskuvbox/Diskuvbox/index.html#val-copy_file) library function. 30 | * Add a CLI command to [src/bin/main.ml](src/bin/main.ml). 31 | * Add a new test file in [src/bin/tests/](src/bin/tests/). Run them 32 | with `dune runtest`. 33 | * Add your new command to the `README.md` document. The help and examples 34 | in that document (for ones that start with ` ```console `) should be automatically generated after you run 35 | `dune build @runmarkdown --auto-promote`. 36 | 37 | Before submitting your PR make sure you have: 38 | 1. Run `opam install ./diskuvbox.opam --deps-only --with-test` 39 | 2. Run `dune build` 40 | 3. Run `dune runtest` 41 | 4. Run `dune build @runmarkdown --auto-promote` 42 | 5. Run `dune build @runlicense --auto-promote` 43 | 6. Run `dune fmt` 44 | -------------------------------------------------------------------------------- /COPYRIGHT: -------------------------------------------------------------------------------- 1 | Copyright 2022 Diskuv, Inc. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /DEVELOPING.md: -------------------------------------------------------------------------------- 1 | # Developing 2 | 3 | ## Upgrading the DKML scripts 4 | 5 | ```bash 6 | opam install ./diskuvbox.opam --deps-only 7 | opam upgrade dkml-workflows 8 | 9 | # Regenerate the DKML workflow scaffolding 10 | opam exec -- generate-setup-dkml-scaffold && dune build '@gen-dkml' --auto-promote && dune build '@ci/setup-dkml/fmt' 11 | ``` 12 | 13 | ## Releasing 14 | 15 | > Do not use `dune-release bistro -p diskuvbox` since it does not work with our GitHub Actions 16 | > generated documentation 17 | 18 | 1. Update the version in `dune-project`. 19 | 2. Run: 20 | 21 | ```shell 22 | dune build 23 | git commit -m "Bump version" *.opam 24 | VERSION=$(awk '$1=="(version" {print $2}' dune-project | tr -d ')') 25 | git tag -a -m "Version $VERSION" $VERSION 26 | git push 27 | 4. Make sure GitHub Actions succeeds 28 | 5. Run: 29 | 30 | ```shell 31 | VERSION=$(awk '$1=="(version" {print $2}' dune-project | tr -d ')') 32 | git push origin $VERSION 33 | opam install dune-release 34 | dune-release distrib -p diskuvbox 35 | dune-release publish distrib -p diskuvbox 36 | dune-release opam pkg -p diskuvbox 37 | dune-release opam submit -p diskuvbox 38 | ``` 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Diskuv Box 2 | 3 | A basic, cross-platform set of commands to manipulate and query the file 4 | system. Available with a [liberal open-source license](LICENSE) as a single 5 | binary (`diskuvbox`, or `diskuvbox.exe` on 6 | Windows) or as an OCaml library with minimal dependencies. 7 | 8 | The single binary design is similar to 9 | [busybox]. You can choose to 10 | run a command like `diskuvbox copy-file` *or (PENDING) make a symlink from 11 | `diskuvbox` to `copy-file`*; either way the "copy-file" tooling will be invoked. 12 | 13 | `diskuvbox` meet the following standards: 14 | * Any printed output will be the same on all operating systems *with the default 15 | options*. For example the file search command [find-up](#diskuvbox-find-up) will 16 | print files it finds in `a/b/c` format, even on Windows. You will only get 17 | operating system specific behavior (ex. printing `a\b\c` on Windows) if you 18 | use options like `--native`. **Feel comfortable using the diskuvbox commands 19 | in CRAM tests and expect scripts**. 20 | * On Windows, any failing command will provide a helpful error message if the 21 | paths are over the Windows default 260 character pathname limit. 22 | * For advanced embedding scenarios, the OCaml bytecode form of `diskuvbox` will 23 | only [dynamically load shared libraries](https://ocaml.org/manual/runtime.html#s:ocamlrun-dllpath) 24 | that are present in standard OCaml installations: dllunix, dllthreads and dllcamlstr. 25 | 26 | **Quick Links** 27 | 28 | | Section | Page | 29 | | ------------ | ---------------------------------------------------------------------- | 30 | | Usage | [Download binaries](#download-binaries) | 31 | | Usage | [Add as an Opam Dependency](#add-as-an-opam-dependency) | 32 | | Usage | [Using in Dune cram tests](#using-in-dune-cram-tests) | 33 | | Usage | [Using in Opam build steps](#using-in-opam-build-steps) | 34 | | Usage | [Using in Dune rules](#using-in-dune-rules) | 35 | | Box Commands | [Box Commands](#box-commands) | 36 | | Box Library | [Box Library](https://diskuv.github.io/diskuvbox/diskuvbox/index.html) | 37 | | Contributing | [Your Contributions](CONTRIBUTORS.md) | 38 | 39 | ## Usage 40 | 41 | ### Download binaries 42 | 43 | Download the latest binary from the [diskuvbox releases] site. 44 | 45 | Available for: 46 | * Windows/Intel 32-bit 47 | * Windows/Intel 64-bit 48 | * macOS/Intel 49 | * macOS/ARM64 (Apple Silicon M1 and M2) 50 | * Linux/Intel 32-bit 51 | * Linux/Intel 64-bit 52 | 53 | **Windows?** On a recently purchased Windows machine you may see: 54 | 55 | ![image that vcruntime140.dll is missing](https://github.com/diskuv/dkml-workflows/raw/bf92f534506340d62055b5d24e04b94dfb07693f/images/vcruntime140_missing.png) 56 | 57 | Install `vc_redist.x86.exe` on 32-bit Windows or `vc_redist.x64.exe` on 64-bit 58 | Windows to avoid that error. Both of those are available from the 59 | [diskuvbox releases] site. 60 | 61 | [diskuvbox releases]: https://github.com/diskuv/diskuvbox/releases 62 | 63 | ### Add as an Opam Dependency 64 | 65 | If you are an OCaml developer who creates or maintains an Opam package, add 66 | the following to your `.opam` file: 67 | 68 | ```powershell 69 | depends: [ 70 | # ... 71 | "diskuvbox" {>= "0.1.0"} 72 | ] 73 | ``` 74 | 75 | or the following to your `dune-project` if Dune auto-generates your opam files: 76 | 77 | ```lisp 78 | (package 79 | ; ... 80 | (depends 81 | ; ... 82 | (diskuvbox (>= 0.1.0)) 83 | ) 84 | ) 85 | ``` 86 | 87 | ### Using in Dune cram tests 88 | 89 | FIRST, make sure you understand and have enabled [Dune Cram Tests](https://dune.readthedocs.io/en/latest/tests.html#cram-tests-1). 90 | 91 | SECOND, make sure you have [Added diskuvbox as an Opam Dependency](#add-as-an-opam-dependency). 92 | 93 | FINALLY, go ahead and use `diskuvbox` in your `.t` cram tests like so: 94 | 95 | 96 | ```console 97 | Use your program. We'll pretend for this example that your program 98 | creates a complex directory structure. 99 | $ install -d a/b/c/d/e/f 100 | $ install -d a/b2/c2/d2/e2/f2 101 | $ install -d a/b2/c3/d3/e3/f3 102 | $ install -d a/b2/c3/d4/e4/f4 103 | $ install -d a/b2/c3/d4/e5/f5 104 | $ install -d a/b2/c3/d4/e5/f6 105 | $ touch a/b/x 106 | $ touch a/b/c/y 107 | $ touch a/b/c/d/z 108 | 109 | Use diskuvbox to print the directory tree. It should be reproducible 110 | on any platform that Diskuv Box supports! 111 | $ diskuvbox tree a --max-depth 10 --encoding UTF-8 112 | a 113 | ├── b/ 114 | │ ├── c/ 115 | │ │ ├── d/ 116 | │ │ │ ├── e/ 117 | │ │ │ │ └── f/ 118 | │ │ │ └── z 119 | │ │ └── y 120 | │ └── x 121 | └── b2/ 122 | ├── c2/ 123 | │ └── d2/ 124 | │ └── e2/ 125 | │ └── f2/ 126 | └── c3/ 127 | ├── d3/ 128 | │ └── e3/ 129 | │ └── f3/ 130 | └── d4/ 131 | ├── e4/ 132 | │ └── f4/ 133 | └── e5/ 134 | ├── f5/ 135 | └── f6/ 136 | ``` 137 | 138 | ### Using in Opam `build` steps 139 | 140 | FIRST, make sure you have [Added diskuvbox as an Opam Dependency](#add-as-an-opam-dependency). 141 | 142 | SECOND, if you are sure that you *only* need diskuvbox for Opam build steps, 143 | change your `.opam` file so it has a `{build}` filter. For example: 144 | 145 | ```powershell 146 | depends: [ 147 | # ... 148 | "diskuvbox" {>= "0.1.0" & build} 149 | ] 150 | ``` 151 | 152 | or in your `dune-project` if you auto-generate your .opam files: 153 | 154 | ```lisp 155 | (package 156 | ; ... 157 | (depends 158 | ; ... 159 | (diskuvbox (and (>= 0.1.0) :build)) 160 | ) 161 | ) 162 | ``` 163 | 164 | FINALLY, go ahead and use `diskuvbox` in your .opam build steps like: 165 | 166 | ```powershell 167 | build: [ 168 | # ... 169 | ["diskuvbox" "copy-file-into" "assets/icon.png" "assets/public.gpg" "%{_:share}%"] 170 | ] 171 | ``` 172 | 173 | ### Using in Dune rules 174 | 175 | FIRST, make sure you have [Added diskuvbox as an Opam Dependency](#add-as-an-opam-dependency). 176 | 177 | THEN, go ahead and use `diskuvbox` as `(run diskuvbox ...)` in the rules of 178 | your `dune` files. 179 | 180 | For example, in the source code for this project we have detailed `dune` rules 181 | that ensure each OCaml source file always has our open-source Apache v2.0 182 | license at the top. We use `(run diskuvbox ...)` so that our already complex 183 | rules don't become even more complicated with platform specific hacks: 184 | 185 | 186 | ```lisp 187 | ; This first rule creates "corrected" source code in the Dune build directory 188 | ; that always has an Apache v2.0 license at the top of each file. 189 | (rule 190 | (targets diskuvbox.corrected.ml diskuvbox.corrected.mli) 191 | (deps 192 | (:license %{project_root}/etc/license-header.txt) 193 | (:conf %{project_root}/etc/headache.conf)) 194 | (action 195 | (progn 196 | ; `headache` adds/replaces headers in source code. It is documented at 197 | ; https://github.com/Frama-C/headache/#readme 198 | ; 199 | ; 1. The `headache` program modifies files in-place, so we make a copy of 200 | ; the original file. 201 | ; 2. On Windows `heachache` can fail with "Permission denied" if we don't 202 | ; set write permissions on the file. 203 | ; `diskuvbox` can accomplish both goals on all its supported platforms. 204 | (run diskuvbox copy-file -m 644 diskuvbox.ml diskuvbox.corrected.ml) 205 | (run diskuvbox copy-file -m 644 diskuvbox.mli diskuvbox.corrected.mli) 206 | ; Add Apache v2.0 license to each file 207 | (run headache -h %{license} -c %{conf} %{targets}) 208 | ; 209 | ; `ocamlformat` is used so that our source code modification is idempotent. 210 | ; (Advanced: Options chosen so that continuous integration tests work with 211 | ; any version of `ocamlformat`.) 212 | (run ocamlformat --inplace --disable-conf-files --enable-outside-detected-project %{targets})))) 213 | 214 | ; These second set of rules let us type: 215 | ; dune build @runlicense --auto-promote 216 | ; 217 | ; Anytime we type that Dune will take the corrected source code from the Dune 218 | ; build directory and use it to modify the original source code. 219 | (rule 220 | (alias runlicense) 221 | (action 222 | (diff diskuvbox.ml diskuvbox.corrected.ml))) 223 | (rule 224 | (alias runlicense) 225 | (action 226 | (diff diskuvbox.mli diskuvbox.corrected.mli))) 227 | ``` 228 | 229 | ## Box commands 230 | 231 | > Looking for the **[OCaml library](https://diskuv.github.io/diskuvbox/diskuvbox/index.html)**? 232 | > The library documentation is 233 | > available at https://diskuv.github.io/diskuvbox/diskuvbox/index.html. 234 | > Just use `opam install diskuvbox` (or `with-dkml opam install diskuvbox` with 235 | > the Diskuv OCaml Windows distribution) to install Diskuv Box in your existing 236 | > OCaml project. **Consider the API unstable until version 1.0.** 237 | 238 | 239 | | Command | Description | 240 | | ------------------------------------------- | ------------------------------------------------------------------------- | 241 | | [copy-dir](#diskuvbox-copy-dir) | Copy content of one or more source directories to a destination directory | 242 | | [copy-file](#diskuvbox-copy-file) | Copy a source file to a destination file | 243 | | [copy-file-into](#diskuvbox-copy-file-into) | Copy one or more files into a destination directory | 244 | | [find-up](#diskuvbox-find-up) | Find a file in the current directory or one of its ancestors | 245 | | [touch](#diskuvbox-touch) | Touch one or more files | 246 | | [tree](#diskuvbox-tree) | Print a directory tree | 247 | 248 | If you would like to add or modify a Box command, head over to 249 | **[Your Contributions](CONTRIBUTORS.md)**. 250 | 251 | ### diskuvbox copy-dir 252 | 253 | ```console 254 | $ diskuvbox copy-dir --help 255 | NAME 256 | diskuvbox-copy-dir - Copy content of one or more source directories to 257 | a destination directory. 258 | 259 | SYNOPSIS 260 | diskuvbox copy-dir [OPTION]… SRCDIR… DESTDIR 261 | 262 | DESCRIPTION 263 | Copy content of one or more SRCDIR... directories to the DESTDIR 264 | directory. copy-dir will follow symlinks. 265 | 266 | ARGUMENTS 267 | DESTDIR (required) 268 | Destination directory. If DESTDIR does not exist it will be 269 | created. 270 | 271 | SRCDIR (required) 272 | One or more source directories to copy. The command fails when a 273 | SRCDIR does not exist. 274 | 275 | OPTIONS 276 | --color=WHEN (absent=auto) 277 | Colorize the output. WHEN must be one of auto, always or never. 278 | 279 | --prefix=VAL 280 | A prefix that will be prepended to each destination file. 281 | 282 | -q, --quiet 283 | Be quiet. Takes over -v and --verbosity. 284 | 285 | --suffix=VAL 286 | A suffix that will be appended to each destination file. 287 | 288 | -v, --verbose 289 | Increase verbosity. Repeatable, but more than twice does not bring 290 | more. 291 | 292 | --verbosity=LEVEL (absent=warning) 293 | Be more or less verbose. LEVEL must be one of quiet, error, 294 | warning, info or debug. Takes over -v. 295 | 296 | COMMON OPTIONS 297 | --help[=FMT] (default=auto) 298 | Show this help in format FMT. The value FMT must be one of auto, 299 | pager, groff or plain. With auto, the format is pager or plain 300 | whenever the TERM env var is dumb or undefined. 301 | 302 | --version 303 | Show version information. 304 | 305 | EXIT STATUS 306 | copy-dir exits with the following status: 307 | 308 | 0 on success. 309 | 310 | 123 on indiscriminate errors reported on standard error. 311 | 312 | 124 on command line parsing errors. 313 | 314 | 125 on unexpected internal errors (bugs). 315 | 316 | SEE ALSO 317 | diskuvbox(1) 318 | 319 | ``` 320 | 321 | ### diskuvbox copy-file 322 | 323 | ```console 324 | $ diskuvbox copy-file --help 325 | NAME 326 | diskuvbox-copy-file - Copy a source file to a destination file. 327 | 328 | SYNOPSIS 329 | diskuvbox copy-file [OPTION]… SRCFILE DESTFILE 330 | 331 | DESCRIPTION 332 | Copy the SRCFILE to the DESTFILE. copy-file will follow symlinks. 333 | 334 | ARGUMENTS 335 | DESTFILE (required) 336 | Destination file. If DESTFILE does not exist it will be created. 337 | 338 | SRCFILE (required) 339 | The source file to copy. The command fails when a SRCFILE does not 340 | exist. 341 | 342 | OPTIONS 343 | --color=WHEN (absent=auto) 344 | Colorize the output. WHEN must be one of auto, always or never. 345 | 346 | -m VAL, --mode=VAL 347 | The chmod mode permission of the destination file, in octal. If 348 | not specified then the chmod mode permission of the source file is 349 | used. Examples: 644, 755. 350 | 351 | --prefix=VAL 352 | A prefix that will be prepended to each destination file. 353 | 354 | -q, --quiet 355 | Be quiet. Takes over -v and --verbosity. 356 | 357 | --suffix=VAL 358 | A suffix that will be appended to each destination file. 359 | 360 | -v, --verbose 361 | Increase verbosity. Repeatable, but more than twice does not bring 362 | more. 363 | 364 | --verbosity=LEVEL (absent=warning) 365 | Be more or less verbose. LEVEL must be one of quiet, error, 366 | warning, info or debug. Takes over -v. 367 | 368 | COMMON OPTIONS 369 | --help[=FMT] (default=auto) 370 | Show this help in format FMT. The value FMT must be one of auto, 371 | pager, groff or plain. With auto, the format is pager or plain 372 | whenever the TERM env var is dumb or undefined. 373 | 374 | --version 375 | Show version information. 376 | 377 | EXIT STATUS 378 | copy-file exits with the following status: 379 | 380 | 0 on success. 381 | 382 | 123 on indiscriminate errors reported on standard error. 383 | 384 | 124 on command line parsing errors. 385 | 386 | 125 on unexpected internal errors (bugs). 387 | 388 | SEE ALSO 389 | diskuvbox(1) 390 | 391 | ``` 392 | 393 | ### diskuvbox copy-file-into 394 | 395 | ```console 396 | $ diskuvbox copy-file-into --help 397 | NAME 398 | diskuvbox-copy-file-into - Copy one or more files into a destination 399 | directory. 400 | 401 | SYNOPSIS 402 | diskuvbox copy-file-into [OPTION]… SRCFILE… DESTDIR 403 | 404 | DESCRIPTION 405 | Copy one or more SRCFILE... files to the DESTDIR directory. 406 | copy-files-into will follow symlinks. 407 | 408 | ARGUMENTS 409 | DESTDIR (required) 410 | Destination directory. If DESTDIR does not exist it will be 411 | created. 412 | 413 | SRCFILE (required) 414 | One or more source files to copy. The command fails when a SRCFILE 415 | does not exist. 416 | 417 | OPTIONS 418 | --color=WHEN (absent=auto) 419 | Colorize the output. WHEN must be one of auto, always or never. 420 | 421 | -m VAL, --mode=VAL 422 | The chmod mode permission of the destination file, in octal. If 423 | not specified then the chmod mode permission of the source file is 424 | used. Examples: 644, 755. 425 | 426 | --prefix=VAL 427 | A prefix that will be prepended to each destination file. 428 | 429 | -q, --quiet 430 | Be quiet. Takes over -v and --verbosity. 431 | 432 | --suffix=VAL 433 | A suffix that will be appended to each destination file. 434 | 435 | -v, --verbose 436 | Increase verbosity. Repeatable, but more than twice does not bring 437 | more. 438 | 439 | --verbosity=LEVEL (absent=warning) 440 | Be more or less verbose. LEVEL must be one of quiet, error, 441 | warning, info or debug. Takes over -v. 442 | 443 | COMMON OPTIONS 444 | --help[=FMT] (default=auto) 445 | Show this help in format FMT. The value FMT must be one of auto, 446 | pager, groff or plain. With auto, the format is pager or plain 447 | whenever the TERM env var is dumb or undefined. 448 | 449 | --version 450 | Show version information. 451 | 452 | EXIT STATUS 453 | copy-file-into exits with the following status: 454 | 455 | 0 on success. 456 | 457 | 123 on indiscriminate errors reported on standard error. 458 | 459 | 124 on command line parsing errors. 460 | 461 | 125 on unexpected internal errors (bugs). 462 | 463 | SEE ALSO 464 | diskuvbox(1) 465 | 466 | ``` 467 | 468 | ### diskuvbox find-up 469 | 470 | ```console 471 | $ diskuvbox find-up --help 472 | NAME 473 | diskuvbox-find-up - Find a file in the current directory or one of its 474 | ancestors. 475 | 476 | SYNOPSIS 477 | diskuvbox find-up [OPTION]… FROMDIR BASENAME… 478 | 479 | DESCRIPTION 480 | Find a file that matches the name as one or more specified FILE... 481 | files in the FROMDIR directory. 482 | 483 | Will print the matching file if found. Otherwise will print nothing. 484 | 485 | ARGUMENTS 486 | BASENAME (required) 487 | One or more basenames to search. The command fails when a BASENAME 488 | is blank or has a directory separator. 489 | 490 | FROMDIR (required) 491 | Directory to search. The command fails when FROMDIR does not 492 | exist. 493 | 494 | OPTIONS 495 | --color=WHEN (absent=auto) 496 | Colorize the output. WHEN must be one of auto, always or never. 497 | 498 | --native 499 | Print files and directories in native format. On Windows the 500 | native format uses backslashes as directory separators, while on 501 | Unix (including macOS) the native format uses forward slashes. If 502 | --native is not specified then all files and directories are 503 | printed with the directory separators as forward slashes. 504 | 505 | -q, --quiet 506 | Be quiet. Takes over -v and --verbosity. 507 | 508 | -v, --verbose 509 | Increase verbosity. Repeatable, but more than twice does not bring 510 | more. 511 | 512 | --verbosity=LEVEL (absent=warning) 513 | Be more or less verbose. LEVEL must be one of quiet, error, 514 | warning, info or debug. Takes over -v. 515 | 516 | COMMON OPTIONS 517 | --help[=FMT] (default=auto) 518 | Show this help in format FMT. The value FMT must be one of auto, 519 | pager, groff or plain. With auto, the format is pager or plain 520 | whenever the TERM env var is dumb or undefined. 521 | 522 | --version 523 | Show version information. 524 | 525 | EXIT STATUS 526 | find-up exits with the following status: 527 | 528 | 0 on success. 529 | 530 | 123 on indiscriminate errors reported on standard error. 531 | 532 | 124 on command line parsing errors. 533 | 534 | 125 on unexpected internal errors (bugs). 535 | 536 | SEE ALSO 537 | diskuvbox(1) 538 | 539 | ``` 540 | 541 | ### diskuvbox touch 542 | 543 | ```console 544 | $ diskuvbox touch --help 545 | NAME 546 | diskuvbox-touch-file - Touch one or more files. 547 | 548 | SYNOPSIS 549 | diskuvbox touch-file [OPTION]… FILE… 550 | 551 | DESCRIPTION 552 | Touch one or more FILE... files. 553 | 554 | ARGUMENTS 555 | FILE (required) 556 | One or more files to touch. If a FILE does not exist it will be 557 | created. 558 | 559 | OPTIONS 560 | --color=WHEN (absent=auto) 561 | Colorize the output. WHEN must be one of auto, always or never. 562 | 563 | -q, --quiet 564 | Be quiet. Takes over -v and --verbosity. 565 | 566 | -v, --verbose 567 | Increase verbosity. Repeatable, but more than twice does not bring 568 | more. 569 | 570 | --verbosity=LEVEL (absent=warning) 571 | Be more or less verbose. LEVEL must be one of quiet, error, 572 | warning, info or debug. Takes over -v. 573 | 574 | COMMON OPTIONS 575 | --help[=FMT] (default=auto) 576 | Show this help in format FMT. The value FMT must be one of auto, 577 | pager, groff or plain. With auto, the format is pager or plain 578 | whenever the TERM env var is dumb or undefined. 579 | 580 | --version 581 | Show version information. 582 | 583 | EXIT STATUS 584 | touch-file exits with the following status: 585 | 586 | 0 on success. 587 | 588 | 123 on indiscriminate errors reported on standard error. 589 | 590 | 124 on command line parsing errors. 591 | 592 | 125 on unexpected internal errors (bugs). 593 | 594 | SEE ALSO 595 | diskuvbox(1) 596 | 597 | ``` 598 | 599 | ### diskuvbox tree 600 | 601 | ```console 602 | $ diskuvbox tree --help 603 | NAME 604 | diskuvbox-tree - Print a directory tree. 605 | 606 | SYNOPSIS 607 | diskuvbox tree [OPTION]… DIR 608 | 609 | DESCRIPTION 610 | Print the directory tree starting at the DIR directory. By default 611 | only the DIR directory (the first level) is printed. Use --max-depth 612 | to print deeper 613 | 614 | ARGUMENTS 615 | DIR (required) 616 | Directory to print. The command fails when DIR does not exist. 617 | 618 | OPTIONS 619 | --color=WHEN (absent=auto) 620 | Colorize the output. WHEN must be one of auto, always or never. 621 | 622 | -d VAL, --max-depth=VAL (absent=0) 623 | Maximum depth to print. A maximum depth of 0 will never print 624 | deeper than the name of the starting directory. A maximum depth of 625 | 1 will, at most, print the contents of the starting directory. 626 | Defaults to 0 627 | 628 | -e VAL, --encoding=VAL (absent=ASCII) 629 | The encoding of the graphic characters printed: ASCII, UTF-8. 630 | Defaults to ASCII 631 | 632 | --native 633 | Print files and directories in native format. On Windows the 634 | native format uses backslashes as directory separators, while on 635 | Unix (including macOS) the native format uses forward slashes. If 636 | --native is not specified then all files and directories are 637 | printed with the directory separators as forward slashes. 638 | 639 | -q, --quiet 640 | Be quiet. Takes over -v and --verbosity. 641 | 642 | -v, --verbose 643 | Increase verbosity. Repeatable, but more than twice does not bring 644 | more. 645 | 646 | --verbosity=LEVEL (absent=warning) 647 | Be more or less verbose. LEVEL must be one of quiet, error, 648 | warning, info or debug. Takes over -v. 649 | 650 | COMMON OPTIONS 651 | --help[=FMT] (default=auto) 652 | Show this help in format FMT. The value FMT must be one of auto, 653 | pager, groff or plain. With auto, the format is pager or plain 654 | whenever the TERM env var is dumb or undefined. 655 | 656 | --version 657 | Show version information. 658 | 659 | EXIT STATUS 660 | tree exits with the following status: 661 | 662 | 0 on success. 663 | 664 | 123 on indiscriminate errors reported on standard error. 665 | 666 | 124 on command line parsing errors. 667 | 668 | 125 on unexpected internal errors (bugs). 669 | 670 | SEE ALSO 671 | diskuvbox(1) 672 | 673 | ``` 674 | 675 | ## Contributions 676 | 677 | Head over to **[Your Contributions](CONTRIBUTORS.md)**. 678 | 679 | ## Acknowledgements 680 | 681 | The first implementations of Diskuv Box were implemented with the assistance of 682 | the [OCaml Software Foundation (OCSF)](http://ocaml-sf.org), 683 | a sub-foundation of the [INRIA Foundation](https://www.inria.fr). 684 | 685 | Two OCaml libraries ([bos](https://erratique.ch/software/bos) and 686 | [cmdliner](https://erratique.ch/software/cmdliner)) are essential to Diskuv Box; 687 | these libraries were created by [Daniel Bünzli](https://erratique.ch/profile). 688 | 689 | ## Status 690 | 691 | | Status | 692 | | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | 693 | | [![Box tests](https://github.com/diskuv/diskuvbox/actions/workflows/test.yml/badge.svg)](https://github.com/diskuv/diskuvbox/actions/workflows/test.yml) | 694 | | [![Syntax check](https://github.com/diskuv/diskuvbox/actions/workflows/syntax.yml/badge.svg)](https://github.com/diskuv/diskuvbox/actions/workflows/syntax.yml) | 695 | 696 | [busybox]: https://en.wikipedia.org/wiki/BusyBox 697 | -------------------------------------------------------------------------------- /ci/build-test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -euf 3 | 4 | usage() { 5 | echo "'--opam-package OPAM_PACKAGE.opam --executable-name EXECUTABLE_NAME' where you have a (executable (public_name EXECUTABLE_NAME) ...) in some 'dune' file" >&2 6 | exit 3 7 | } 8 | OPTION=$1 9 | shift 10 | [ "$OPTION" = "--opam-package" ] || usage 11 | OPAM_PACKAGE=$1 12 | shift 13 | OPTION=$1 14 | shift 15 | [ "$OPTION" = "--executable-name" ] || usage 16 | EXECUTABLE_NAME=$1 17 | shift 18 | 19 | # If (executable (public_name EXECUTABLE_NAME) ...) already has .exe then executable will 20 | # have .exe. Otherwise it depends on exe_ext. 21 | case "$EXECUTABLE_NAME" in 22 | *.exe) suffix_ext="" ;; 23 | *) suffix_ext="${exe_ext:-}" ;; 24 | esac 25 | 26 | # Set HERE 27 | HERE=$(dirname "$0") 28 | HERE=$(cd "$HERE" && pwd) 29 | if [ -x /usr/bin/cygpath ]; then 30 | HERE_MIXED=$(/usr/bin/cygpath -aw "$HERE") 31 | else 32 | HERE_MIXED=$HERE 33 | fi 34 | 35 | # shellcheck disable=SC2154 36 | echo " 37 | ============= 38 | build-test.sh 39 | ============= 40 | . 41 | --------- 42 | Arguments 43 | --------- 44 | OPAM_PACKAGE=$OPAM_PACKAGE 45 | EXECUTABLE_NAME=$EXECUTABLE_NAME 46 | . 47 | ------ 48 | Matrix 49 | ------ 50 | dkml_host_abi=$dkml_host_abi 51 | abi_pattern=$abi_pattern 52 | opam_root=$opam_root 53 | exe_ext=${exe_ext:-} 54 | . 55 | ------- 56 | Derived 57 | ------- 58 | suffix_ext=$suffix_ext 59 | . 60 | " 61 | 62 | # PATH. Add opamrun 63 | if [ -n "${CI_PROJECT_DIR:-}" ]; then 64 | export PATH="$CI_PROJECT_DIR/.ci/sd4/opamrun:$PATH" 65 | elif [ -n "${PC_PROJECT_DIR:-}" ]; then 66 | export PATH="$PC_PROJECT_DIR/.ci/sd4/opamrun:$PATH" 67 | elif [ -n "${GITHUB_WORKSPACE:-}" ]; then 68 | export PATH="$GITHUB_WORKSPACE/.ci/sd4/opamrun:$PATH" 69 | else 70 | export PATH="$PWD/.ci/sd4/opamrun:$PATH" 71 | fi 72 | 73 | # Initial Diagnostics 74 | opamrun switch 75 | opamrun list --switch dkml 76 | opamrun var 77 | opamrun config report 78 | opamrun exec --switch dkml -- ocamlc -config 79 | 80 | # Update 81 | opamrun update 82 | 83 | # Define regression tests 84 | regression_tests() { 85 | # https://github.com/diskuv/diskuvbox/issues/1 86 | if command -v truncate >/dev/null 2>/dev/null; then 87 | truncate -s 20MB test32bit 88 | else 89 | dd if=/dev/zero of=test32bit bs=1024 count=0 seek=20480 90 | fi 91 | opamrun exec --switch dkml -- env OCAMLRUNPARAM=b dune exec -- src/bin/main.exe copy-file -vv test32bit dest/1/2/test32bit 92 | rm -f test32bit dest/1/2/test32bit 93 | rmdir dest/1/2 94 | rmdir dest/1 95 | rmdir dest 96 | } 97 | 98 | # Configure cross-compiling in Opam 99 | OPAM_PKGNAME=${OPAM_PACKAGE%.opam} 100 | # 0. Some host ABIs can cross-compile; set config for those. 101 | case "$dkml_host_abi" in 102 | darwin_x86_64) 103 | dunecontext='(context (default (targets native darwin_arm64)))' 104 | toolchain=darwin_arm64;; 105 | *) 106 | dunecontext=''; 107 | toolchain='' 108 | esac 109 | if [ -n "$dunecontext" ]; then 110 | # 1. Get a copy of opam-installer from the 'two' secondary switch. We'll just need the binary. 111 | install -d .ci/cross 112 | opamrun list --switch two 113 | opamrun install opam-installer --switch two --yes 114 | opaminstaller="${opam_root}/two/bin/opam-installer" 115 | if [ -x "$opaminstaller.exe" ]; then 116 | install "$opaminstaller.exe" .ci/cross/opam-installer.exe 117 | else 118 | install "$opaminstaller" .ci/cross/opam-installer 119 | fi 120 | # 2. Use Dune-ified packages so can cross-compile (same principle underneath Opam monorepo). 121 | # Opam monorepo doesn't work yet for dkml-base-compiler. 122 | opamrun repository --switch dkml add dune-universe git+https://github.com/dune-universe/opam-overlays.git 123 | # 3. Set pre-build-commands so that each Opam package has a correct dune-workspace when 124 | # cross-compiling. 125 | option_args=$(printf 'pre-build-commands=["%s" "%s" "%s" "%s"]' \ 126 | "$HERE_MIXED/crosscompiling-workspace-generator.sh" \ 127 | '%{name}%' \ 128 | '%{_:build}%/dune-workspace' \ 129 | "$dunecontext") 130 | opamrun option --switch dkml "$option_args" 131 | # 4. Each Opam package must install its cross-compiled libraries into Opam switch 132 | option_args=$(printf 'post-install-commands=["%s" "%s" "%s" "%s" "%s" "%s" "%s" "%s" "%s"]' \ 133 | "$HERE_MIXED/crosscompiling-opam-installer.sh" \ 134 | "$PWD/.ci/cross/opam-installer" \ 135 | "%{name}%-${toolchain}.install" \ 136 | "%{name}%" \ 137 | "%{lib}%" \ 138 | "%{man}%" \ 139 | "%{prefix}%" \ 140 | "%{stublibs}%" \ 141 | "%{toplevel}%") 142 | opamrun option --switch dkml "$option_args" 143 | 144 | # Pin to the Dune-ified packages. Technically most of these are unnecessary 145 | # because they will be repeated in `opamrun install` but some are 146 | # required to remove DKML's standard MSVC pins 147 | opamrun pin astring --switch dkml -k version 0.8.5+dune --no-action --yes 148 | opamrun pin base-bytes --switch dkml -k version base --no-action --yes 149 | opamrun pin bos --switch dkml -k version 0.2.1+dune --no-action --yes 150 | opamrun pin cmdliner --switch dkml -k version 1.1.1+dune --no-action --yes 151 | opamrun pin fmt --switch dkml -k version 0.9.0+dune --no-action --yes 152 | opamrun pin fpath --switch dkml -k version 0.7.3+dune --no-action --yes 153 | opamrun pin logs --switch dkml -k version 0.7.0+dune2 --no-action --yes 154 | opamrun pin ptime --switch dkml -k version 1.0.0+dune2 --no-action --yes 155 | opamrun pin rresult --switch dkml -k version 0.7.0+dune --no-action --yes 156 | opamrun pin seq --switch dkml -k version base+dune --no-action --yes 157 | opamrun pin uucp --switch dkml -k version 14.0.0+dune --no-action --yes 158 | opamrun pin uuseg --switch dkml -k version 14.0.0+dune --no-action --yes 159 | opamrun pin uutf --switch dkml -k version 1.0.3+dune --no-action --yes 160 | # * no --with-test since likely can't run cross-compiled 161 | # architecture without an emulator 162 | opamrun install "./${OPAM_PKGNAME}.opam" --switch dkml --deps-only --yes 163 | 164 | # Test on host ABI 165 | opamrun exec --switch dkml -- dune build -p diskuvbox @runtest 166 | regression_tests 167 | 168 | # Cross-compile to target ABI 169 | opamrun exec --switch dkml -- dune build -p diskuvbox -x "${toolchain}" _build/default/diskuvbox.install "_build/default.${toolchain}/diskuvbox-${toolchain}.install" 170 | else 171 | # If config switches from cross-compiling to host compiling, reset cross-compiling 172 | opamrun option --switch dkml 'pre-build-commands=' 173 | opamrun option --switch dkml 'post-install-commands=' 174 | 175 | # Build 176 | opamrun install "./${OPAM_PKGNAME}.opam" --switch dkml --deps-only --yes 177 | opamrun exec --switch dkml -- dune build -p diskuvbox @runtest _build/default/diskuvbox.install 178 | 179 | # Test 180 | regression_tests 181 | fi 182 | 183 | # Prereq: Diagnostics 184 | case "${dkml_host_abi}" in 185 | linux_*) 186 | if command -v apk; then 187 | apk add file 188 | fi ;; 189 | esac 190 | 191 | # Copy the installed binaries (including cross-compiled ones) from Opam into dist/ folder. 192 | # Name the binaries with the target ABI since GitHub Releases are flat namespaces. 193 | install -d dist/ 194 | mv _build/install/default "_build/install/default.${dkml_host_abi}" 195 | set +f 196 | for i in _build/install/default.*; do 197 | target_abi=$(basename "$i" | sed s/default.//) 198 | if [ -e "_build/install/default.${target_abi}/bin/${OPAM_PKGNAME}.exe" ]; then 199 | install -v "_build/install/default.${target_abi}/bin/${OPAM_PKGNAME}.exe" "dist/${target_abi}-${OPAM_PKGNAME}.exe" 200 | file "dist/${target_abi}-${OPAM_PKGNAME}.exe" 201 | else 202 | install -v "_build/install/default.${target_abi}/bin/${OPAM_PKGNAME}" "dist/${target_abi}-${OPAM_PKGNAME}" 203 | file "dist/${target_abi}-${OPAM_PKGNAME}" 204 | fi 205 | done 206 | 207 | # For Windows you must ask your users to first install the vc_redist executable. 208 | # Confer: https://github.com/diskuv/dkml-workflows#distributing-your-windows-executables 209 | case "${dkml_host_abi}" in 210 | windows_x86_64) wget -O dist/vc_redist.x64.exe https://aka.ms/vs/17/release/vc_redist.x64.exe ;; 211 | windows_x86) wget -O dist/vc_redist.x86.exe https://aka.ms/vs/17/release/vc_redist.x86.exe ;; 212 | windows_arm64) wget -O dist/vc_redist.arm64.exe https://aka.ms/vs/17/release/vc_redist.arm64.exe ;; 213 | esac 214 | -------------------------------------------------------------------------------- /ci/crosscompiling-opam-installer.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # usage: crosscompiling-opam-installer.sh \ 3 | # "$(opam var opam-installer-bin)/opam-installer" \ 4 | # "%{name}%-<>.install" \ 5 | # "%{name}%" \ 6 | # "%{lib}%" \ 7 | # "%{man}%" \ 8 | # "%{prefix}%" \ 9 | # "%{stublibs}%" \ 10 | # "%{toplevel}%" 11 | # where <> is darwin_arm64 or another target ABI. 12 | 13 | opam_installer=$1 14 | shift 15 | install_file=$1 16 | shift 17 | name=$1 18 | shift 19 | libdir=$1 20 | shift 21 | mandir=$1 22 | shift 23 | prefix=$1 24 | shift 25 | stubsdir=$1 26 | shift 27 | topdir=$1 28 | shift 29 | 30 | if [ ! -e "$install_file" ]; then 31 | # if this package does not install any files, skip it 32 | exit 0 33 | fi 34 | 35 | exec "$opam_installer" \ 36 | --install "$install_file" \ 37 | --name="$name" \ 38 | --libdir="$libdir" \ 39 | --mandir="$mandir" \ 40 | --prefix="$prefix" \ 41 | --stubsdir="$stubsdir" \ 42 | --topdir="$topdir" 43 | -------------------------------------------------------------------------------- /ci/crosscompiling-workspace-generator.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # usage: crosscompiling-workspace-generator.sh "%{name}%" "%{_:build}%/dune-workspace" DUNE_CONTEXT 3 | # args: 4 | # DUNE_CONTEXT: The contents of your desired Dune workspace file except the "(lang dune 2.9)" header line. 5 | # example: "(context (default (targets native darwin_arm64)))" 6 | name=$1 7 | shift 8 | duneworkspace_filename=$1 9 | shift 10 | duneworkspace_content=$1 11 | shift 12 | 13 | # Get out of here if no cross-compiling needed 14 | case "$name" in 15 | base-bigarray|base-threads|base-unix|ocaml-system|dkml-base-compiler|ocaml-config|ocaml|dune|conf-dkml-cross-toolchain) 16 | # Don't need to, and don't want to, cross-compile these packages. 17 | exit 0 18 | ;; 19 | esac 20 | 21 | # Add dune-workspace 22 | # We populate (lang dune 2.9) so can guarantee there is a newline. Dune 3.2.0 and likely many 23 | # other versions require the (lang ...) clause to be by itself. 24 | printf "(lang dune 2.9)\n%s" "$duneworkspace_content" > "$duneworkspace_filename" 25 | -------------------------------------------------------------------------------- /ci/prepare-release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -euf 3 | 4 | # Restructure multi-ABI directories 5 | _release="$(pwd)/_release" 6 | install -d "$_release" 7 | 8 | cd dist 9 | find . -mindepth 1 -maxdepth 1 -type d | while read -r distname; do 10 | rsync -av "$distname/" "$_release" 11 | done 12 | cd .. 13 | 14 | # Display files to be distributed 15 | cd _release 16 | ls -R 17 | cd .. 18 | -------------------------------------------------------------------------------- /ci/setup-dkml/gh-darwin/post/action.yml: -------------------------------------------------------------------------------- 1 | # setup-dkml 2 | # Short form: sd4 3 | 4 | name: post-dkml 5 | author: Diskuv, Inc. 6 | description: Teardown after building OCaml native executables for Darwin 7 | 8 | runs: 9 | using: "composite" 10 | 11 | steps: 12 | - name: Full matrix variables 13 | shell: bash 14 | # Every matrix variable lookup in this Action should use the output of this step. Even 15 | # the matrix variables that the user must specify (ex. dkml_host_abi) should be 16 | # referenced using [steps.full_matrix_vars.outputs.dkml_host_abi] rather than 17 | # [matrix.dkml_host_abi] so that there is a single place to edit for variable changes. 18 | id: full_matrix_vars 19 | run: | 20 | # Select correct Darwin matrix variables 21 | case "${{ matrix.dkml_host_abi }}" in 22 | 23 | darwin_x86_64) 24 | dkml_host_os='darwin'; 25 | opam_root_cacheable='/Users/runner/.opam'; 26 | abi_pattern='macos-darwin_all'; 27 | gh_os='macos-latest'; 28 | gh_unix_shell='sh'; 29 | bootstrap_opam_version='2.2.0-dkml20220801T155940Z'; 30 | dkml_host_abi='darwin_x86_64'; 31 | opam_root='/Users/runner/.opam' ;; 32 | 33 | *) echo "FATAL: Unsupported dkml_host_abi=$dkml_host_abi in Darwin action.yml"; exit 107 ;; 34 | esac 35 | 36 | add() { 37 | echo "$1=$2" | tee -a $GITHUB_OUTPUT | tee -a $GITHUB_ENV 38 | } 39 | 40 | add dkml_host_abi "$dkml_host_abi" 41 | add abi_pattern "$abi_pattern" 42 | add opam_root "$opam_root" 43 | add opam_root_cacheable "$opam_root_cacheable" 44 | add exe_ext "${exe_ext:-}" 45 | add bootstrap_opam_version "${bootstrap_opam_version:-}" 46 | add ocaml_options "${ocaml_options:-}" 47 | 48 | - name: Teardown DKML build apparatus 49 | shell: bash 50 | env: 51 | _STUB_FOR_AUTOGEN: "ON" # 52 | 53 | # autogen from global_env_vars. 54 | DEFAULT_DKML_COMPILER: '4.12.1-v1.0.2' 55 | PIN_BASE: 'v0.14.3' 56 | PIN_BIGSTRINGAF: '0.8.0' 57 | PIN_CORE_KERNEL: 'v0.14.2' 58 | PIN_CTYPES_FOREIGN: '0.19.2-windowssupport-r4' 59 | PIN_CTYPES: '0.19.2-windowssupport-r4' 60 | PIN_CURLY: '0.2.1-windows-env_r2' 61 | PIN_DIGESTIF: '1.0.1' 62 | PIN_DUNE: '2.9.3+shim.1.0.2~r0' 63 | PIN_DUNE_CONFIGURATOR: '2.9.3' 64 | PIN_DKML_APPS: '1.0.1' 65 | PIN_OCAMLBUILD: '0.14.0' 66 | PIN_OCAMLFIND: '1.9.1' 67 | PIN_OCP_INDENT: '1.8.2-windowssupport' 68 | PIN_PPX_EXPECT: 'v0.14.1' 69 | PIN_PTIME: '0.8.6-msvcsupport' 70 | PIN_TIME_NOW: 'v0.14.0' 71 | PIN_WITH_DKML: '1.0.1' 72 | run: | 73 | sh .ci/sd4/run-teardown-dkml.sh GITHUB_WORKSPACE "$GITHUB_WORKSPACE" 74 | -------------------------------------------------------------------------------- /ci/setup-dkml/gh-darwin/post/dune: -------------------------------------------------------------------------------- 1 | ; DO NOT EDIT THIS FILE. It is auto-generated by generate-setup-dkml-scaffold 2 | ; Typical upgrade steps: 3 | ; opam upgrade dkml-workflows && opam exec -- generate-setup-dkml-scaffold && dune build '@gen-dkml' --auto-promote 4 | 5 | (rule 6 | (alias gen-dkml) 7 | (target action.gen.yml) 8 | (action 9 | (setenv 10 | OCAMLRUNPARAM 11 | b 12 | (run gh-dkml-action-yml --phase post --output-darwin %{target})))) 13 | 14 | (rule 15 | (alias gen-dkml) 16 | (action 17 | (diff action.yml action.gen.yml))) 18 | -------------------------------------------------------------------------------- /ci/setup-dkml/gh-darwin/pre/dune: -------------------------------------------------------------------------------- 1 | ; DO NOT EDIT THIS FILE. It is auto-generated by generate-setup-dkml-scaffold 2 | ; Typical upgrade steps: 3 | ; opam upgrade dkml-workflows && opam exec -- generate-setup-dkml-scaffold && dune build '@gen-dkml' --auto-promote 4 | 5 | (rule 6 | (alias gen-dkml) 7 | (target action.gen.yml) 8 | (action 9 | (setenv 10 | OCAMLRUNPARAM 11 | b 12 | (run gh-dkml-action-yml --phase pre --output-darwin %{target})))) 13 | 14 | (rule 15 | (alias gen-dkml) 16 | (action 17 | (diff action.yml action.gen.yml))) 18 | -------------------------------------------------------------------------------- /ci/setup-dkml/gh-linux/post/action.yml: -------------------------------------------------------------------------------- 1 | # setup-dkml 2 | # Short form: sd4 3 | 4 | name: post-dkml 5 | author: Diskuv, Inc. 6 | description: Teardown after building OCaml native executables for Linux 7 | 8 | runs: 9 | using: "composite" 10 | 11 | steps: 12 | - name: Full matrix variables 13 | shell: bash 14 | # Every matrix variable lookup in this Action should use the output of this step. Even 15 | # the matrix variables that the user must specify (ex. dkml_host_abi) should be 16 | # referenced using [steps.full_matrix_vars.outputs.dkml_host_abi] rather than 17 | # [matrix.dkml_host_abi] so that there is a single place to edit for variable changes. 18 | id: full_matrix_vars 19 | run: | 20 | # Select correct Linux matrix variables 21 | case "${{ matrix.dkml_host_abi }}" in 22 | 23 | linux_x86) 24 | dkml_host_os='linux'; 25 | opam_root_cacheable='.ci/o'; 26 | abi_pattern='manylinux2014-linux_x86'; 27 | gh_os='ubuntu-latest'; 28 | comment='(CentOS 7, etc.)'; 29 | gh_unix_shell='sh'; 30 | bootstrap_opam_version='2.2.0-dkml20220801T155940Z'; 31 | dkml_host_abi='linux_x86'; 32 | opam_root='.ci/o'; 33 | in_docker='true'; 34 | dockcross_image='dockcross/manylinux2014-x86'; 35 | dockcross_run_extra_args='--platform linux/386' ;; 36 | 37 | linux_x86_64) 38 | dkml_host_os='linux'; 39 | opam_root_cacheable='.ci/o'; 40 | abi_pattern='manylinux2014-linux_x86_64'; 41 | gh_os='ubuntu-latest'; 42 | comment='(CentOS 7, etc.)'; 43 | gh_unix_shell='sh'; 44 | bootstrap_opam_version='2.2.0-dkml20220801T155940Z'; 45 | dkml_host_abi='linux_x86_64'; 46 | opam_root='.ci/o'; 47 | dockcross_image='dockcross/manylinux2014-x64'; 48 | dockcross_run_extra_args='--platform linux/amd64'; 49 | in_docker='true' ;; 50 | 51 | *) echo "FATAL: Unsupported dkml_host_abi=$dkml_host_abi in Linux action.yml"; exit 107 ;; 52 | esac 53 | 54 | add() { 55 | echo "$1=$2" | tee -a $GITHUB_OUTPUT | tee -a $GITHUB_ENV 56 | } 57 | 58 | add dkml_host_abi "$dkml_host_abi" 59 | add abi_pattern "$abi_pattern" 60 | add opam_root "$opam_root" 61 | add opam_root_cacheable "$opam_root_cacheable" 62 | add exe_ext "${exe_ext:-}" 63 | add bootstrap_opam_version "${bootstrap_opam_version:-}" 64 | add ocaml_options "${ocaml_options:-}" 65 | 66 | - name: Teardown DKML build apparatus 67 | shell: bash 68 | env: 69 | _STUB_FOR_AUTOGEN: "ON" # 70 | 71 | # autogen from global_env_vars. 72 | DEFAULT_DKML_COMPILER: '4.12.1-v1.0.2' 73 | PIN_BASE: 'v0.14.3' 74 | PIN_BIGSTRINGAF: '0.8.0' 75 | PIN_CORE_KERNEL: 'v0.14.2' 76 | PIN_CTYPES_FOREIGN: '0.19.2-windowssupport-r4' 77 | PIN_CTYPES: '0.19.2-windowssupport-r4' 78 | PIN_CURLY: '0.2.1-windows-env_r2' 79 | PIN_DIGESTIF: '1.0.1' 80 | PIN_DUNE: '2.9.3+shim.1.0.2~r0' 81 | PIN_DUNE_CONFIGURATOR: '2.9.3' 82 | PIN_DKML_APPS: '1.0.1' 83 | PIN_OCAMLBUILD: '0.14.0' 84 | PIN_OCAMLFIND: '1.9.1' 85 | PIN_OCP_INDENT: '1.8.2-windowssupport' 86 | PIN_PPX_EXPECT: 'v0.14.1' 87 | PIN_PTIME: '0.8.6-msvcsupport' 88 | PIN_TIME_NOW: 'v0.14.0' 89 | PIN_WITH_DKML: '1.0.1' 90 | run: | 91 | sh .ci/sd4/run-teardown-dkml.sh GITHUB_WORKSPACE "$GITHUB_WORKSPACE" 92 | -------------------------------------------------------------------------------- /ci/setup-dkml/gh-linux/post/dune: -------------------------------------------------------------------------------- 1 | ; DO NOT EDIT THIS FILE. It is auto-generated by generate-setup-dkml-scaffold 2 | ; Typical upgrade steps: 3 | ; opam upgrade dkml-workflows && opam exec -- generate-setup-dkml-scaffold && dune build '@gen-dkml' --auto-promote 4 | 5 | (rule 6 | (alias gen-dkml) 7 | (target action.gen.yml) 8 | (action 9 | (setenv 10 | OCAMLRUNPARAM 11 | b 12 | (run gh-dkml-action-yml --phase post --output-linux %{target})))) 13 | 14 | (rule 15 | (alias gen-dkml) 16 | (action 17 | (diff action.yml action.gen.yml))) 18 | -------------------------------------------------------------------------------- /ci/setup-dkml/gh-linux/pre/dune: -------------------------------------------------------------------------------- 1 | ; DO NOT EDIT THIS FILE. It is auto-generated by generate-setup-dkml-scaffold 2 | ; Typical upgrade steps: 3 | ; opam upgrade dkml-workflows && opam exec -- generate-setup-dkml-scaffold && dune build '@gen-dkml' --auto-promote 4 | 5 | (rule 6 | (alias gen-dkml) 7 | (target action.gen.yml) 8 | (action 9 | (setenv 10 | OCAMLRUNPARAM 11 | b 12 | (run gh-dkml-action-yml --phase pre --output-linux %{target})))) 13 | 14 | (rule 15 | (alias gen-dkml) 16 | (action 17 | (diff action.yml action.gen.yml))) 18 | -------------------------------------------------------------------------------- /ci/setup-dkml/gh-windows/post/action.yml: -------------------------------------------------------------------------------- 1 | # setup-dkml 2 | # Short form: sd4 3 | 4 | # Any GitHub Job that includes this action must be in a strategy matrix. 5 | # The matrix variables must include: 6 | # - gh_os: windows-2019 7 | # abi_pattern: win32-windows_x86 8 | # dkml_host_abi: windows_x86 9 | 10 | name: post-dkml 11 | author: Diskuv, Inc. 12 | description: Teardown after building OCaml native executables for Windows 13 | 14 | runs: 15 | using: "composite" 16 | 17 | steps: 18 | - name: Full matrix variables 19 | shell: bash # bash on Windows is Git Bash (an non-upgradable MSYS2 system) 20 | # Every matrix variable lookup in this Action should use the output of this step. Even 21 | # the matrix variables that the user must specify (ex. dkml_host_abi) should be 22 | # referenced using [steps.full_matrix_vars.outputs.dkml_host_abi] rather than 23 | # [matrix.dkml_host_abi] so that there is a single place to edit for variable changes. 24 | id: full_matrix_vars 25 | run: | 26 | # Select correct Windows matrix variables 27 | case "${{ matrix.dkml_host_abi }}" in 28 | 29 | windows_x86) 30 | dkml_host_os='windows'; 31 | opam_root_cacheable='D:/.opam'; 32 | abi_pattern='win32-windows_x86'; 33 | gh_os='windows-2019'; 34 | gh_unix_shell='msys2 {0}'; 35 | msys2_system='MINGW32'; 36 | msys2_packages='mingw-w64-i686-pkg-config'; 37 | exe_ext='.exe'; 38 | bootstrap_opam_version='2.2.0-dkml20220801T155940Z'; 39 | opam_abi='windows_x86'; 40 | dkml_host_abi='windows_x86'; 41 | opam_root='D:/.opam'; 42 | vsstudio_hostarch='x64'; 43 | vsstudio_arch='x86'; 44 | ocaml_options='ocaml-option-32bit' ;; 45 | 46 | windows_x86_64) 47 | dkml_host_os='windows'; 48 | opam_root_cacheable='D:/.opam'; 49 | abi_pattern='win32-windows_x86_64'; 50 | gh_os='windows-2019'; 51 | gh_unix_shell='msys2 {0}'; 52 | msys2_system='CLANG64'; 53 | msys2_packages='mingw-w64-clang-x86_64-pkg-config'; 54 | exe_ext='.exe'; 55 | bootstrap_opam_version='2.2.0-dkml20220801T155940Z'; 56 | opam_abi='windows_x86_64'; 57 | dkml_host_abi='windows_x86_64'; 58 | opam_root='D:/.opam'; 59 | vsstudio_hostarch='x64'; 60 | vsstudio_arch='x64' ;; 61 | 62 | *) echo "FATAL: Unsupported dkml_host_abi=$dkml_host_abi in Windows action.yml"; exit 107 ;; 63 | esac 64 | 65 | add() { 66 | echo "$1=$2" | tee -a $GITHUB_OUTPUT | tee -a $GITHUB_ENV 67 | } 68 | 69 | add dkml_host_abi "$dkml_host_abi" 70 | add abi_pattern "$abi_pattern" 71 | add opam_root "$opam_root" 72 | add opam_root_cacheable "$opam_root_cacheable" 73 | add exe_ext "${exe_ext:-}" 74 | add bootstrap_opam_version "${bootstrap_opam_version:-}" 75 | add ocaml_options "${ocaml_options:-}" 76 | 77 | - name: Teardown DKML build apparatus 78 | shell: msys2 {0} 79 | env: 80 | _STUB_FOR_AUTOGEN: "ON" # 81 | 82 | # autogen from global_env_vars. 83 | DEFAULT_DKML_COMPILER: '4.12.1-v1.0.2' 84 | PIN_BASE: 'v0.14.3' 85 | PIN_BIGSTRINGAF: '0.8.0' 86 | PIN_CORE_KERNEL: 'v0.14.2' 87 | PIN_CTYPES_FOREIGN: '0.19.2-windowssupport-r4' 88 | PIN_CTYPES: '0.19.2-windowssupport-r4' 89 | PIN_CURLY: '0.2.1-windows-env_r2' 90 | PIN_DIGESTIF: '1.0.1' 91 | PIN_DUNE: '2.9.3+shim.1.0.2~r0' 92 | PIN_DUNE_CONFIGURATOR: '2.9.3' 93 | PIN_DKML_APPS: '1.0.1' 94 | PIN_OCAMLBUILD: '0.14.0' 95 | PIN_OCAMLFIND: '1.9.1' 96 | PIN_OCP_INDENT: '1.8.2-windowssupport' 97 | PIN_PPX_EXPECT: 'v0.14.1' 98 | PIN_PTIME: '0.8.6-msvcsupport' 99 | PIN_TIME_NOW: 'v0.14.0' 100 | PIN_WITH_DKML: '1.0.1' 101 | run: | 102 | sh .ci/sd4/run-teardown-dkml.sh GITHUB_WORKSPACE "$GITHUB_WORKSPACE" 103 | -------------------------------------------------------------------------------- /ci/setup-dkml/gh-windows/post/dune: -------------------------------------------------------------------------------- 1 | ; DO NOT EDIT THIS FILE. It is auto-generated by generate-setup-dkml-scaffold 2 | ; Typical upgrade steps: 3 | ; opam upgrade dkml-workflows && opam exec -- generate-setup-dkml-scaffold && dune build '@gen-dkml' --auto-promote 4 | 5 | (rule 6 | (alias gen-dkml) 7 | (target action.gen.yml) 8 | (action 9 | (setenv 10 | OCAMLRUNPARAM 11 | b 12 | (run gh-dkml-action-yml --phase post --output-windows %{target})))) 13 | 14 | (rule 15 | (alias gen-dkml) 16 | (action 17 | (diff action.yml action.gen.yml))) 18 | -------------------------------------------------------------------------------- /ci/setup-dkml/gh-windows/pre/dune: -------------------------------------------------------------------------------- 1 | ; DO NOT EDIT THIS FILE. It is auto-generated by generate-setup-dkml-scaffold 2 | ; Typical upgrade steps: 3 | ; opam upgrade dkml-workflows && opam exec -- generate-setup-dkml-scaffold && dune build '@gen-dkml' --auto-promote 4 | 5 | (rule 6 | (alias gen-dkml) 7 | (target action.gen.yml) 8 | (action 9 | (setenv 10 | OCAMLRUNPARAM 11 | b 12 | (run gh-dkml-action-yml --phase pre --output-windows %{target})))) 13 | 14 | (rule 15 | (alias gen-dkml) 16 | (action 17 | (diff action.yml action.gen.yml))) 18 | -------------------------------------------------------------------------------- /ci/setup-dkml/gl/dune: -------------------------------------------------------------------------------- 1 | ; DO NOT EDIT THIS FILE. It is auto-generated by generate-setup-dkml-scaffold 2 | ; Typical upgrade steps: 3 | ; opam upgrade dkml-workflows && opam exec -- generate-setup-dkml-scaffold && dune build '@gen-dkml' --auto-promote 4 | 5 | (rule 6 | (target setup-dkml.gen.gitlab-ci.yml) 7 | (alias gen-dkml) 8 | (action 9 | (setenv 10 | OCAMLRUNPARAM 11 | b 12 | (run gl-setup-dkml-yml --output-file %{target})))) 13 | 14 | (rule 15 | (alias gen-dkml) 16 | (action 17 | (diff setup-dkml.gitlab-ci.yml setup-dkml.gen.gitlab-ci.yml))) 18 | -------------------------------------------------------------------------------- /ci/setup-dkml/pc/dune: -------------------------------------------------------------------------------- 1 | ; DO NOT EDIT THIS FILE. It is auto-generated by generate-setup-dkml-scaffold 2 | ; Typical upgrade steps: 3 | ; opam upgrade dkml-workflows && opam exec -- generate-setup-dkml-scaffold && dune build '@gen-dkml' --auto-promote 4 | 5 | ; windows_x86 6 | 7 | (rule 8 | (target setup-dkml-windows_x86-gen.ps1) 9 | (action 10 | (setenv 11 | OCAMLRUNPARAM 12 | b 13 | (run pc-setup-dkml --output-windows_x86 %{target})))) 14 | 15 | (rule 16 | (alias gen-dkml) 17 | (action 18 | (diff setup-dkml-windows_x86.ps1 setup-dkml-windows_x86-gen.ps1))) 19 | 20 | ; windows_x86_64 21 | 22 | (rule 23 | (target setup-dkml-windows_x86_64-gen.ps1) 24 | (action 25 | (setenv 26 | OCAMLRUNPARAM 27 | b 28 | (run pc-setup-dkml --output-windows_x86_64 %{target})))) 29 | 30 | (rule 31 | (alias gen-dkml) 32 | (action 33 | (diff setup-dkml-windows_x86_64.ps1 setup-dkml-windows_x86_64-gen.ps1))) 34 | 35 | ; darwin_x86_64 36 | 37 | (rule 38 | (target setup-dkml-darwin_x86_64-gen.sh) 39 | (action 40 | (setenv 41 | OCAMLRUNPARAM 42 | b 43 | (run pc-setup-dkml --output-darwin_x86_64 %{target})))) 44 | 45 | (rule 46 | (alias gen-dkml) 47 | (action 48 | (diff setup-dkml-darwin_x86_64.sh setup-dkml-darwin_x86_64-gen.sh))) 49 | 50 | ; linux_x86 51 | 52 | (rule 53 | (target setup-dkml-linux_x86-gen.sh) 54 | (action 55 | (setenv 56 | OCAMLRUNPARAM 57 | b 58 | (run pc-setup-dkml --output-linux_x86 %{target})))) 59 | 60 | (rule 61 | (alias gen-dkml) 62 | (action 63 | (diff setup-dkml-linux_x86.sh setup-dkml-linux_x86-gen.sh))) 64 | 65 | ; linux_x86_64 66 | 67 | (rule 68 | (target setup-dkml-linux_x86_64-gen.sh) 69 | (action 70 | (setenv 71 | OCAMLRUNPARAM 72 | b 73 | (run pc-setup-dkml --output-linux_x86_64 %{target})))) 74 | 75 | (rule 76 | (alias gen-dkml) 77 | (action 78 | (diff setup-dkml-linux_x86_64.sh setup-dkml-linux_x86_64-gen.sh))) 79 | -------------------------------------------------------------------------------- /ci/setup-dkml/pc/setup-dkml-windows_x86.ps1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diskuv/diskuvbox/0e07c70e57c785f23041ae02ebca03ee007fe10c/ci/setup-dkml/pc/setup-dkml-windows_x86.ps1 -------------------------------------------------------------------------------- /ci/setup-dkml/pc/setup-dkml-windows_x86_64.ps1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diskuv/diskuvbox/0e07c70e57c785f23041ae02ebca03ee007fe10c/ci/setup-dkml/pc/setup-dkml-windows_x86_64.ps1 -------------------------------------------------------------------------------- /cmake/FindDkToolScripts.cmake: -------------------------------------------------------------------------------- 1 | ########################################################################## 2 | # File: dktool/cmake/FindDkToolScripts.cmake # 3 | # # 4 | # Copyright 2023 Diskuv, Inc. # 5 | # # 6 | # Licensed under the Open Software License version 3.0 # 7 | # (the "License"); you may not use this file except in compliance # 8 | # with the License. You may obtain a copy of the License at # 9 | # # 10 | # https://opensource.org/license/osl-3-0-php/ # 11 | # # 12 | ########################################################################## 13 | 14 | # Recommendation: Place this file in source control. 15 | # Auto-generated by `./dk dksdk.project.new` of dktool. 16 | 17 | include(FetchContent) 18 | 19 | function(parse_dktool_command_line) 20 | # The first argument is . All dots will be replaced with a 21 | # triple underscore as a convenience and to be pretty for the user. 22 | # However, we do not error if no is given ... we'll do 23 | # that later. 24 | set(command) 25 | set(expected_function_name) 26 | set(quotedArgs "") 27 | if(ARGC EQUAL 0 OR (ARGC EQUAL 1 AND ARGV0 STREQUAL HELP)) 28 | message(NOTICE [[Usage: 29 | ./dk HELP 30 | ./dk [args] 31 | ]]) 32 | else() 33 | set(command ${ARGV0}) 34 | string(REPLACE "." "___" expected_function_name ${command}) 35 | message(VERBOSE "Searching for ${expected_function_name}") 36 | 37 | # Parse the remainder of the arguments [args] 38 | # * Use technique from [Professional CMake: A Practical Guide - Forwarding Command Arguments] 39 | # to be able to forward arguments correctly to an inner function (the function). 40 | cmake_parse_arguments(PARSE_ARGV 1 FWD "" "" "") 41 | foreach(arg IN LISTS FWD_UNPARSED_ARGUMENTS) 42 | string(APPEND quotedArgs " [===[${arg}]===]") 43 | endforeach() 44 | endif() 45 | 46 | # Set policies (we are in a new EVAL CODE context) 47 | # Included scripts do automatic cmake_policy PUSH and POP 48 | if(POLICY CMP0011) 49 | cmake_policy(SET CMP0011 NEW) 50 | endif() 51 | # Allow GIT_SUBMODULES empty to mean no submodules 52 | if(POLICY CMP0097) 53 | cmake_policy(SET CMP0097 NEW) 54 | endif() 55 | 56 | # Setup the binary directory 57 | if(NOT DKTOOL_WORKDIR) 58 | message(FATAL_ERROR "Illegal state. Expecting DKTOOL_WORKDIR") 59 | endif() 60 | set(CMAKE_BINARY_DIR ${DKTOOL_WORKDIR}) 61 | set(CMAKE_CURRENT_BINARY_DIR ${CMAKE_BINARY_DIR}) 62 | 63 | # Search in all the user scripts 64 | set(dot_function_names) 65 | file(GLOB_RECURSE command_files 66 | LIST_DIRECTORIES FALSE 67 | RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}/cmake/scripts 68 | cmake/scripts/*.cmake) 69 | foreach(command_file IN LISTS command_files) 70 | # Normalize and lowercase 71 | cmake_path(NORMAL_PATH command_file) 72 | string(TOLOWER "${command_file}" command_file) 73 | cmake_path(REMOVE_EXTENSION command_file OUTPUT_VARIABLE command_file_no_ext) 74 | 75 | # Convert to list 76 | string(REPLACE "/" ";" command_stems_no_namespace ${command_file_no_ext}) 77 | 78 | # Make a pretty description only for validation 79 | set(pretty_stems ${command_stems_no_namespace}) 80 | list(TRANSFORM pretty_stems PREPEND "'") 81 | list(TRANSFORM pretty_stems APPEND "'") 82 | string(JOIN ", " pretty_stems_str ${pretty_stems}) 83 | string(REGEX REPLACE ",([^,]*)" " and \\1" pretty_stems_str "${pretty_stems_str}") 84 | 85 | # Validate that only alphanumeric with underscores (but not the reserved three underscores) 86 | string(REGEX MATCH "[/a-z0-9_]*" only_alphanum_and_underscores "${command_file_no_ext}") 87 | if(NOT only_alphanum_and_underscores STREQUAL "${command_file_no_ext}") 88 | message(WARNING "Ignoring user script ${CMAKE_CURRENT_SOURCE_DIR}/${command_file}. 89 | The stems of the user script (${pretty_stems_str}) must only contain letters, numbers and underscores.") 90 | continue() 91 | endif() 92 | string(FIND "${command_file_no_ext}" "___" reserved_underscores) 93 | if(reserved_underscores GREATER_EQUAL 0) 94 | message(WARNING "Ignoring user script ${CMAKE_CURRENT_SOURCE_DIR}/${command_file}. 95 | No stem of the user script (${pretty_stems_str}) can contain a triple underscore ('___').") 96 | continue() 97 | endif() 98 | 99 | # Translate dev/xxx.cmake to the "user" namespaced function name 100 | # `user__dev__xxx` and `user.dev.xxx`. 101 | set(command_stems ${command_stems_no_namespace}) 102 | list(PREPEND command_stems "user") 103 | string(JOIN "___" command_function_name ${command_stems}) 104 | string(JOIN "." dot_function_name ${command_stems}) 105 | list(APPEND dot_function_names ${dot_function_name}) 106 | 107 | # In a new scope (to avoid a global, leaky namespace) register the function. 108 | message(VERBOSE "Shimming ${command_function_name}") 109 | cmake_language(EVAL CODE " 110 | function(${command_function_name}) 111 | include(\"${CMAKE_CURRENT_SOURCE_DIR}/cmake/scripts/${command_file}\") 112 | if(COMMAND run) 113 | run(${quotedArgs}) 114 | else() 115 | message(FATAL_ERROR [[The user script ${CMAKE_CURRENT_SOURCE_DIR}/cmake/scripts/${command_file} was missing: 116 | function(run) 117 | # Your user code 118 | endfunction() 119 | ]]) 120 | endif() 121 | endfunction() 122 | ") 123 | endforeach() 124 | 125 | # Include all the system scripts. 126 | # - Since the system scripts come after the user scripts, the user scripts 127 | # don't override the system scripts unless the user scripts use deferred 128 | # hooks or redefine CMake built-in functions. Regardless, the user 129 | # scripts are namespaced with `user__` prefix 130 | if(NOT IS_DIRECTORY cmake/scripts/dksdk) 131 | # If this project (ex. dktool) has the system scripts, it must 132 | # have all of them. Otherwise we download the system scripts. 133 | FetchContent_Populate(dktool 134 | QUIET 135 | GIT_REPOSITORY https://gitlab.com/diskuv/dktool.git 136 | GIT_TAG 1.0 137 | # As of 3.25.3 the bug https://gitlab.kitware.com/cmake/cmake/-/issues/24578 138 | # has still not been fixed. That means empty strings get removed. 139 | # ExternalProject_Add(GIT_SUBMODULES) in dktool-subbuild/CMakeLists.txt 140 | # means fetch all submodules. 141 | # https://gitlab.kitware.com/cmake/cmake/-/issues/20579#note_734045 142 | # has a workaround. 143 | GIT_SUBMODULES cmake # Non-git-submodule dir that already exists 144 | GIT_SUBMODULES_RECURSE OFF 145 | ) 146 | file(GLOB_RECURSE system_command_files 147 | LIST_DIRECTORIES FALSE 148 | RELATIVE ${dktool_SOURCE_DIR}/cmake/scripts 149 | ${dktool_SOURCE_DIR}/cmake/scripts/dkml/*.cmake 150 | ${dktool_SOURCE_DIR}/cmake/scripts/dksdk/*.cmake) 151 | foreach(command_file IN LISTS system_command_files) 152 | # Normalize and lowercase 153 | cmake_path(NORMAL_PATH command_file) 154 | string(TOLOWER "${command_file}" command_file) 155 | cmake_path(REMOVE_EXTENSION command_file OUTPUT_VARIABLE command_file_no_ext) 156 | 157 | # Convert to list 158 | string(REPLACE "/" ";" command_stems_no_namespace ${command_file_no_ext}) 159 | 160 | # Translate dksdk/xxx.cmake to the function name `dksdk__xxx` and `dksdk.xxx` 161 | set(command_stems ${command_stems_no_namespace}) 162 | string(JOIN "___" command_function_name ${command_stems}) 163 | string(JOIN "." dot_function_name ${command_stems}) 164 | list(APPEND dot_function_names ${dot_function_name}) 165 | 166 | # In a new scope (to avoid a global, leaky namespace) register the function. 167 | message(VERBOSE "Shimming ${command_function_name}") 168 | cmake_language(EVAL CODE " 169 | function(${command_function_name}) 170 | include(\"${dktool_SOURCE_DIR}/cmake/scripts/${command_file}\") 171 | if(COMMAND run) 172 | run(${quotedArgs}) 173 | else() 174 | message(FATAL_ERROR [[The system script ${dktool_SOURCE_DIR}/cmake/scripts/${command_file} was missing: 175 | function(run) 176 | # The system code 177 | endfunction() 178 | ]]) 179 | endif() 180 | endfunction() 181 | ") 182 | 183 | endforeach() 184 | endif() 185 | 186 | # Pretty function names that are available 187 | set(pretty_function_names ${dot_function_names}) 188 | list(TRANSFORM pretty_function_names PREPEND " ") 189 | list(TRANSFORM pretty_function_names APPEND "\n") 190 | string(JOIN "" str_pretty_function_names ${pretty_function_names}) 191 | 192 | # Exit if no 193 | if(NOT command) 194 | message(NOTICE "The following commands are available: 195 | ${str_pretty_function_names}") 196 | return() 197 | endif() 198 | 199 | # Validate the exists 200 | if(NOT COMMAND ${expected_function_name}) 201 | message(FATAL_ERROR "No command '${command}' exists. The following commands are available: 202 | ${str_pretty_function_names}") 203 | message(FATAL_ERROR "No command '${command}' exists") 204 | endif() 205 | 206 | # Call the function 207 | cmake_language(EVAL CODE "${expected_function_name}()") 208 | endfunction() 209 | 210 | # DkML data home 211 | if(WIN32) 212 | set(DKML_DATA_HOME "$ENV{LOCALAPPDATA}/Programs/DiskuvOCaml") 213 | elseif(DEFINED ENV{XDG_DATA_HOME}) 214 | set(DKML_DATA_HOME "$ENV{XDG_DATA_HOME}/diskuv-ocaml") 215 | else() 216 | set(DKML_DATA_HOME "$ENV{HOME}/.local/share/diskuv-ocaml") 217 | endif() 218 | 219 | # DkSDK data home 220 | if(WIN32) 221 | set(DKSDK_DATA_HOME "$ENV{LOCALAPPDATA}/Programs/DkSDK") 222 | elseif(DEFINED ENV{XDG_DATA_HOME}) 223 | set(DKSDK_DATA_HOME "$ENV{XDG_DATA_HOME}/dksdk") 224 | else() 225 | set(DKSDK_DATA_HOME "$ENV{HOME}/.local/share/dksdk") 226 | endif() 227 | 228 | # Splat DKTOOL_CMDLINE 229 | cmake_language(EVAL CODE "parse_dktool_command_line(${DKTOOL_CMDLINE})") 230 | -------------------------------------------------------------------------------- /diskuvbox-maintain.opam: -------------------------------------------------------------------------------- 1 | # This file is generated by dune, edit dune-project instead 2 | opam-version: "2.0" 3 | version: "0.2.0" 4 | synopsis: "Maintenance for diskuvbox: formatting, licenses, embedded docs" 5 | description: 6 | "Maintenance tools that minimize the dependencies for diskuvbox itself." 7 | maintainer: ["opensource+diskuv-ocaml@support.diskuv.com"] 8 | authors: ["Diskuv, Inc. "] 9 | license: "Apache-2.0" 10 | homepage: "https://github.com/diskuv/diskuvbox" 11 | doc: "https://diskuv.github.io/diskuvbox/diskuvbox/index.html" 12 | bug-reports: "https://github.com/diskuv/diskuvbox/issues" 13 | depends: [ 14 | "dune" {>= "2.9"} 15 | "diskuvbox" {= version} 16 | "dkml-workflows" {>= "1.1.0"} 17 | "headache" {>= "1.05"} 18 | "ocamlformat" {= "0.19.0"} 19 | "odoc" {with-doc} 20 | ] 21 | build: [ 22 | ["dune" "subst"] {dev} 23 | [ 24 | "dune" 25 | "build" 26 | "-p" 27 | name 28 | "-j" 29 | jobs 30 | "--promote-install-files=false" 31 | "@install" 32 | "@runtest" {with-test} 33 | "@doc" {with-doc} 34 | ] 35 | ["dune" "install" "-p" name "--create-install-files" name] 36 | ] 37 | dev-repo: "git+https://github.com/diskuv/diskuvbox.git" 38 | -------------------------------------------------------------------------------- /diskuvbox.opam: -------------------------------------------------------------------------------- 1 | # This file is generated by dune, edit dune-project instead 2 | opam-version: "2.0" 3 | version: "0.2.0" 4 | synopsis: "Cross-platform basic set of script commands" 5 | description: 6 | "A cross-platform basic set of script commands. Available as a single binary (`diskuvbox`, or `diskuvbox.exe` on Windows) and as an OCaml library." 7 | maintainer: ["opensource+diskuv-ocaml@support.diskuv.com"] 8 | authors: ["Diskuv, Inc. "] 9 | license: "Apache-2.0" 10 | homepage: "https://github.com/diskuv/diskuvbox" 11 | doc: "https://diskuv.github.io/diskuvbox/diskuvbox/index.html" 12 | bug-reports: "https://github.com/diskuv/diskuvbox/issues" 13 | depends: [ 14 | "dune" {>= "2.9"} 15 | "odoc" {>= "1.5.3" & with-doc} 16 | "ocaml" {>= "4.10.0"} 17 | "ppx_deriving" {>= "5.2.1"} 18 | "bos" {>= "0.2.0"} 19 | "fmt" {>= "0.8.9"} 20 | "logs" {>= "0.7.0"} 21 | "result" {>= "1.5"} 22 | "mdx" {>= "2.0.0" & with-test} 23 | "cmdliner" {>= "1.1.0"} 24 | ] 25 | dev-repo: "git+https://github.com/diskuv/diskuvbox.git" 26 | # Until Dune 3+ the auto-generated '.opam' will have an invalid ["dune" "install" ...] step 27 | # that messes up with cross-compilation. Customized it to remove it. 28 | build: [ 29 | ["dune" "subst"] {dev} 30 | ["dune" "build" "-p" name "-j" jobs "@install" "@runtest" {with-test} "@doc" {with-doc}] 31 | ] 32 | -------------------------------------------------------------------------------- /diskuvbox.opam.template: -------------------------------------------------------------------------------- 1 | # Until Dune 3+ the auto-generated '.opam' will have an invalid ["dune" "install" ...] step 2 | # that messes up with cross-compilation. Customized it to remove it. 3 | build: [ 4 | ["dune" "subst"] {dev} 5 | ["dune" "build" "-p" name "-j" jobs "@install" "@runtest" {with-test} "@doc" {with-doc}] 6 | ] 7 | -------------------------------------------------------------------------------- /dk: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | ########################################################################## 3 | # File: dktool/dk # 4 | # # 5 | # Copyright 2023 Diskuv, Inc. # 6 | # # 7 | # Licensed under the Open Software License version 3.0 # 8 | # (the "License"); you may not use this file except in compliance # 9 | # with the License. You may obtain a copy of the License at # 10 | # # 11 | # https://opensource.org/license/osl-3-0-php/ # 12 | # # 13 | ########################################################################## 14 | 15 | # Recommendation: Place this file in source control. 16 | # Auto-generated by `./dk dksdk.project.new` of dktool. 17 | # 18 | # Invoking: ./dk 19 | # That works in Powershell on Windows, and in Unix. Copy-and-paste works! 20 | # 21 | # Purpose: Install CMake if not already. Then invoke CMake. 22 | 23 | set -euf 24 | 25 | dk_pwd=$PWD 26 | 27 | # --- Imports of dkml-runtime-common's crossplatform-functions.sh --- 28 | 29 | # Get standard locations of Unix system binaries like `/usr/bin/mv` (or `/bin/mv`). 30 | # 31 | # Will not return anything in `/usr/local/bin` or `/usr/sbin`. Use when you do not 32 | # know whether the PATH has been set correctly, or when you do not know if the 33 | # system binary exists. 34 | # 35 | # At some point in the future, this function will error out if the required system binaries 36 | # do not exist. Most system binaries are common to all Unix/Linux/macOS installations but 37 | # some (like `comm`) may need to be installed for proper functioning of DKML. 38 | # 39 | # Outputs: 40 | # - env:DKMLSYS_MV - Location of `mv` 41 | # - env:DKMLSYS_CHMOD - Location of `chmod` 42 | # - env:DKMLSYS_UNAME - Location of `uname` 43 | # - env:DKMLSYS_ENV - Location of `env` 44 | # - env:DKMLSYS_AWK - Location of `awk` 45 | # - env:DKMLSYS_SED - Location of `sed` 46 | # - env:DKMLSYS_COMM - Location of `comm` 47 | # - env:DKMLSYS_INSTALL - Location of `install` 48 | # - env:DKMLSYS_RM - Location of `rm` 49 | # - env:DKMLSYS_SORT - Location of `sort` 50 | # - env:DKMLSYS_CAT - Location of `cat` 51 | # - env:DKMLSYS_STAT - Location of `stat` 52 | # - env:DKMLSYS_GREP - Location of `grep` 53 | # - env:DKMLSYS_CURL - Location of `curl` (empty if not found) 54 | # - env:DKMLSYS_WGET - Location of `wget` (empty if not found) 55 | # - env:DKMLSYS_TR - Location of `tr` 56 | autodetect_system_binaries() { 57 | if [ -z "${DKMLSYS_MV:-}" ]; then 58 | if [ -x /usr/bin/mv ]; then 59 | DKMLSYS_MV=/usr/bin/mv 60 | else 61 | DKMLSYS_MV=/bin/mv 62 | fi 63 | fi 64 | if [ -z "${DKMLSYS_CHMOD:-}" ]; then 65 | if [ -x /usr/bin/chmod ]; then 66 | DKMLSYS_CHMOD=/usr/bin/chmod 67 | else 68 | DKMLSYS_CHMOD=/bin/chmod 69 | fi 70 | fi 71 | if [ -z "${DKMLSYS_UNAME:-}" ]; then 72 | if [ -x /usr/bin/uname ]; then 73 | DKMLSYS_UNAME=/usr/bin/uname 74 | else 75 | DKMLSYS_UNAME=/bin/uname 76 | fi 77 | fi 78 | if [ -z "${DKMLSYS_ENV:-}" ]; then 79 | if [ -x /usr/bin/env ]; then 80 | DKMLSYS_ENV=/usr/bin/env 81 | else 82 | DKMLSYS_ENV=/bin/env 83 | fi 84 | fi 85 | if [ -z "${DKMLSYS_AWK:-}" ]; then 86 | if [ -x /usr/bin/awk ]; then 87 | DKMLSYS_AWK=/usr/bin/awk 88 | else 89 | DKMLSYS_AWK=/bin/awk 90 | fi 91 | fi 92 | if [ -z "${DKMLSYS_SED:-}" ]; then 93 | if [ -x /usr/bin/sed ]; then 94 | DKMLSYS_SED=/usr/bin/sed 95 | else 96 | DKMLSYS_SED=/bin/sed 97 | fi 98 | fi 99 | if [ -z "${DKMLSYS_COMM:-}" ]; then 100 | if [ -x /usr/bin/comm ]; then 101 | DKMLSYS_COMM=/usr/bin/comm 102 | else 103 | DKMLSYS_COMM=/bin/comm 104 | fi 105 | fi 106 | if [ -z "${DKMLSYS_INSTALL:-}" ]; then 107 | if [ -x /usr/bin/install ]; then 108 | DKMLSYS_INSTALL=/usr/bin/install 109 | else 110 | DKMLSYS_INSTALL=/bin/install 111 | fi 112 | fi 113 | if [ -z "${DKMLSYS_RM:-}" ]; then 114 | if [ -x /usr/bin/rm ]; then 115 | DKMLSYS_RM=/usr/bin/rm 116 | else 117 | DKMLSYS_RM=/bin/rm 118 | fi 119 | fi 120 | if [ -z "${DKMLSYS_SORT:-}" ]; then 121 | if [ -x /usr/bin/sort ]; then 122 | DKMLSYS_SORT=/usr/bin/sort 123 | else 124 | DKMLSYS_SORT=/bin/sort 125 | fi 126 | fi 127 | if [ -z "${DKMLSYS_CAT:-}" ]; then 128 | if [ -x /usr/bin/cat ]; then 129 | DKMLSYS_CAT=/usr/bin/cat 130 | else 131 | DKMLSYS_CAT=/bin/cat 132 | fi 133 | fi 134 | if [ -z "${DKMLSYS_STAT:-}" ]; then 135 | if [ -x /usr/bin/stat ]; then 136 | DKMLSYS_STAT=/usr/bin/stat 137 | else 138 | DKMLSYS_STAT=/bin/stat 139 | fi 140 | fi 141 | if [ -z "${DKMLSYS_GREP:-}" ]; then 142 | if [ -x /usr/bin/grep ]; then 143 | DKMLSYS_GREP=/usr/bin/grep 144 | else 145 | DKMLSYS_GREP=/bin/grep 146 | fi 147 | fi 148 | if [ -z "${DKMLSYS_CURL:-}" ]; then 149 | if [ -x /usr/bin/curl ]; then 150 | DKMLSYS_CURL=/usr/bin/curl 151 | elif [ -x /bin/curl ]; then 152 | DKMLSYS_CURL=/bin/curl 153 | else 154 | DKMLSYS_CURL= 155 | fi 156 | fi 157 | if [ -z "${DKMLSYS_WGET:-}" ]; then 158 | if [ -x /usr/bin/wget ]; then 159 | DKMLSYS_WGET=/usr/bin/wget 160 | elif [ -x /bin/wget ]; then 161 | DKMLSYS_WGET=/bin/wget 162 | else 163 | DKMLSYS_WGET= 164 | fi 165 | fi 166 | if [ -z "${DKMLSYS_TR:-}" ]; then 167 | if [ -x /usr/bin/tr ]; then 168 | DKMLSYS_TR=/usr/bin/tr 169 | else 170 | DKMLSYS_TR=/bin/tr 171 | fi 172 | fi 173 | export DKMLSYS_MV DKMLSYS_CHMOD DKMLSYS_UNAME DKMLSYS_ENV DKMLSYS_AWK DKMLSYS_SED DKMLSYS_COMM DKMLSYS_INSTALL 174 | export DKMLSYS_RM DKMLSYS_SORT DKMLSYS_CAT DKMLSYS_STAT DKMLSYS_GREP DKMLSYS_CURL DKMLSYS_WGET DKMLSYS_TR 175 | } 176 | 177 | # Is a Windows build machine if we are in a MSYS2 or Cygwin environment. 178 | # 179 | # Better alternatives 180 | # ------------------- 181 | # 182 | # 1. If you are checking to see if you should do a cygpath, then just guard it 183 | # like so: 184 | # if [ -x /usr/bin/cygpath ]; then 185 | # do_something $(/usr/bin/cygpath ...) ... 186 | # fi 187 | # This clearly guards what you are about to do (cygpath) with what you will 188 | # need (cygpath). 189 | # 2. is_arg_windows_platform 190 | is_unixy_windows_build_machine() { 191 | if is_msys2_msys_build_machine || is_cygwin_build_machine; then 192 | return 0 193 | fi 194 | return 1 195 | } 196 | 197 | # Is a MSYS2 environment with the MSYS or MINGW64 subsystem? 198 | # * MSYS2 can also do MinGW 32-bit and 64-bit subsystems. Used by Diskuv OCaml 199 | # * MINGW64 used by Git Bash (aka. GitHub Actions `shell: bash`) 200 | # https://www.msys2.org/docs/environments/ 201 | is_msys2_msys_build_machine() { 202 | if [ -e /usr/bin/msys-2.0.dll ] && { 203 | [ "${MSYSTEM:-}" = "MSYS" ] || [ "${MSYSTEM:-}" = "MINGW64" ] || [ "${MSYSTEM:-}" = "UCRT64" ] || [ "${MSYSTEM:-}" = "CLANG64" ] || [ "${MSYSTEM:-}" = "MINGW32" ] || [ "${MSYSTEM:-}" = "CLANG32" ] || [ "${MSYSTEM:-}" = "CLANGARM64" ] 204 | }; then 205 | return 0 206 | fi 207 | return 1 208 | } 209 | 210 | is_cygwin_build_machine() { 211 | if [ -e /usr/bin/cygwin1.dll ]; then 212 | return 0 213 | fi 214 | return 1 215 | } 216 | 217 | # Tries to find the host ABI. 218 | # 219 | # Beware: This function uses `uname` probing which is inaccurate during 220 | # cross-compilation. 221 | # 222 | # Outputs: 223 | # - env:BUILDHOST_ARCH will contain the host ABI. 224 | autodetect_buildhost_arch() { 225 | # Set DKMLSYS_* 226 | autodetect_system_binaries 227 | 228 | autodetect_buildhost_arch_SYSTEM=$("$DKMLSYS_UNAME" -s) 229 | autodetect_buildhost_arch_MACHINE=$("$DKMLSYS_UNAME" -m) 230 | # list from https://en.wikipedia.org/wiki/Uname and https://stackoverflow.com/questions/45125516/possible-values-for-uname-m 231 | case "${autodetect_buildhost_arch_SYSTEM}-${autodetect_buildhost_arch_MACHINE}" in 232 | Linux-armv7*) 233 | BUILDHOST_ARCH=linux_arm32v7;; 234 | Linux-armv6* | Linux-arm) 235 | BUILDHOST_ARCH=linux_arm32v6;; 236 | Linux-aarch64 | Linux-arm64 | Linux-armv8*) 237 | BUILDHOST_ARCH=linux_arm64;; 238 | Linux-i386 | Linux-i686) 239 | BUILDHOST_ARCH=linux_x86;; 240 | Linux-x86_64) 241 | BUILDHOST_ARCH=linux_x86_64;; 242 | Darwin-arm64) 243 | BUILDHOST_ARCH=darwin_arm64;; 244 | Darwin-x86_64) 245 | BUILDHOST_ARCH=darwin_x86_64;; 246 | *-i386 | *-i686) 247 | if is_unixy_windows_build_machine; then 248 | BUILDHOST_ARCH=windows_x86 249 | else 250 | printf "%s\n" "FATAL: Unsupported build machine type obtained from 'uname -s' and 'uname -m': $autodetect_buildhost_arch_SYSTEM and $autodetect_buildhost_arch_MACHINE" >&2 251 | exit 1 252 | fi 253 | ;; 254 | *-x86_64) 255 | if is_unixy_windows_build_machine; then 256 | BUILDHOST_ARCH=windows_x86_64 257 | else 258 | printf "%s\n" "FATAL: Unsupported build machine type obtained from 'uname -s' and 'uname -m': $autodetect_buildhost_arch_SYSTEM and $autodetect_buildhost_arch_MACHINE" >&2 259 | exit 1 260 | fi 261 | ;; 262 | *) 263 | # Since: 264 | # 1) MSYS2 does not run on ARM/ARM64 (https://www.msys2.org/docs/environments/) 265 | # 2) MSVC does not use ARM/ARM64 as host machine (https://docs.microsoft.com/en-us/cpp/build/building-on-the-command-line?view=msvc-160) 266 | # we do not support Windows ARM/ARM64 as a build machine 267 | printf "%s\n" "FATAL: Unsupported build machine type obtained from 'uname -s' and 'uname -m': $autodetect_buildhost_arch_SYSTEM and $autodetect_buildhost_arch_MACHINE" >&2 268 | exit 1 269 | ;; 270 | esac 271 | } 272 | 273 | # A function that will try to print an ISO8601 timestamp, but will fallback to 274 | # the system default. Always uses UTC timezone. 275 | try_iso8601_timestamp() { 276 | date -u -Iseconds 2>/dev/null || TZ=UTC date 277 | } 278 | 279 | # A function that will print the command and possibly time it (if and only if it uses a full path to 280 | # an executable, so that 'time' does not fail on internal shell functions). 281 | # If --return-error-code is the first argument or LOG_TRACE_RETURN_ERROR_CODE=ON, then instead of exiting the 282 | # function will return the error code. 283 | log_trace() { 284 | log_trace_RETURN=${LOG_TRACE_RETURN_ERROR_CODE:-OFF} 285 | 286 | log_trace_1="$1" 287 | if [ "$log_trace_1" = "--return-error-code" ]; then 288 | shift 289 | log_trace_RETURN=ON 290 | fi 291 | 292 | if [ "${DKML_BUILD_TRACE:-OFF}" = ON ]; then 293 | printf "[%s] %s\n" "$(try_iso8601_timestamp)" "+ $*" >&2 294 | if [ -x "$1" ]; then 295 | time "$@" 296 | else 297 | "$@" 298 | fi 299 | else 300 | # use judgement so we essentially have log at an INFO level 301 | case "$1" in 302 | rm|cp) 303 | # debug level. only show when DKML_BUILD_TRACE=ON 304 | ;; 305 | git|make|ocaml_configure|ocaml_make|make_host|make_target) 306 | # info level. and can show entire command without polluting the screen 307 | printf "[%s] %s\n" "$(try_iso8601_timestamp)" "$*" >&2 308 | ;; 309 | *) printf "[%s] %s\n" "$(try_iso8601_timestamp)" "$1" >&2 310 | esac 311 | "$@" 312 | fi 313 | log_trace_ec="$?" 314 | if [ "$log_trace_ec" -ne 0 ]; then 315 | if [ "$log_trace_RETURN" = ON ]; then 316 | return "$log_trace_ec" 317 | else 318 | printf "FATAL: Command failed with exit code %s: %s\n" "$log_trace_ec" "$*" >&2 319 | exit "$log_trace_ec" 320 | fi 321 | fi 322 | } 323 | 324 | # [sha256compute FILE] writes the SHA256 checksum (hex encoded) of file FILE to the standard output. 325 | sha256compute() { 326 | sha256compute_FILE="$1" 327 | shift 328 | # For reasons unclear doing the following in MSYS2: 329 | # sha256sum 'Z:\source\README.md' 330 | # will produce a backslash like: 331 | # \5518c76ed7234a153941fb7bc94b6e91d9cb8f1c4e22daf169a59b5878c3fc8a *Z:\\source\\README.md 332 | # So always cygpath the filename if available 333 | if [ -x /usr/bin/cygpath ]; then 334 | sha256compute_FILE=$(/usr/bin/cygpath -a "$sha256compute_FILE") 335 | fi 336 | 337 | if [ -x /usr/bin/shasum ]; then 338 | /usr/bin/shasum -a 256 "$sha256compute_FILE" | awk '{print $1}' 339 | elif [ -x /usr/bin/sha256sum ]; then 340 | /usr/bin/sha256sum "$sha256compute_FILE" | awk '{print $1}' 341 | else 342 | printf "FATAL: %s\n" "No sha256 checksum utility found" >&2 343 | exit 107 344 | fi 345 | } 346 | 347 | # [sha256check FILE SUM] checks that the file FILE has a SHA256 checksum (hex encoded) of SUM. 348 | # The function will return nonzero (and exit with failure if `set -e` is enabled) if the checksum does not match. 349 | sha256check() { 350 | sha256check_FILE="$1" 351 | shift 352 | sha256check_SUM="$1" 353 | shift 354 | if [ -x /usr/bin/shasum ]; then 355 | printf "%s %s" "$sha256check_SUM" "$sha256check_FILE" | /usr/bin/shasum -a 256 -c >&2 356 | elif [ -x /usr/bin/sha256sum ]; then 357 | printf "%s %s" "$sha256check_SUM" "$sha256check_FILE" | /usr/bin/sha256sum -c >&2 358 | else 359 | printf "FATAL: %s\n" "No sha256 checksum utility found" >&2 360 | exit 107 361 | fi 362 | } 363 | 364 | # [downloadfile URL FILE SUM] downloads from URL into FILE and verifies the SHA256 checksum of SUM. 365 | # If the FILE already exists with the correct checksum it is not redownloaded. 366 | # The function will exit with failure if the checksum does not match. 367 | downloadfile() { 368 | downloadfile_URL="$1" 369 | shift 370 | downloadfile_FILE="$1" 371 | shift 372 | downloadfile_SUM="$1" 373 | shift 374 | 375 | # Set DKMLSYS_* 376 | autodetect_system_binaries 377 | 378 | if [ -e "$downloadfile_FILE" ]; then 379 | if sha256check "$downloadfile_FILE" "$downloadfile_SUM"; then 380 | return 0 381 | else 382 | $DKMLSYS_RM -f "$downloadfile_FILE" 383 | fi 384 | fi 385 | if [ "${CI:-}" = true ]; then 386 | if [ -n "$DKMLSYS_CURL" ]; then 387 | log_trace "$DKMLSYS_CURL" -L -s "$downloadfile_URL" -o "$downloadfile_FILE".tmp 388 | elif [ -n "$DKMLSYS_WGET" ]; then 389 | log_trace "$DKMLSYS_WGET" -q -O "$downloadfile_FILE".tmp "$downloadfile_URL" 390 | else 391 | echo "No curl or wget available on the system paths" >&2 392 | exit 107 393 | fi 394 | else 395 | if [ -n "$DKMLSYS_CURL" ]; then 396 | log_trace "$DKMLSYS_CURL" -L "$downloadfile_URL" -o "$downloadfile_FILE".tmp 397 | elif [ -n "$DKMLSYS_WGET" ]; then 398 | log_trace "$DKMLSYS_WGET" -O "$downloadfile_FILE".tmp "$downloadfile_URL" 399 | else 400 | echo "No curl or wget available on the system paths" >&2 401 | exit 107 402 | fi 403 | fi 404 | if ! sha256check "$downloadfile_FILE".tmp "$downloadfile_SUM"; then 405 | printf "%s\n" "FATAL: Encountered a corrupted or compromised download from $downloadfile_URL" >&2 406 | exit 1 407 | fi 408 | $DKMLSYS_MV "$downloadfile_FILE".tmp "$downloadfile_FILE" 409 | } 410 | 411 | # --- Environment detection --- 412 | 413 | # Set DKMLSYS_* 414 | autodetect_system_binaries 415 | 416 | # Find host ABI. Set in BUILDHOST_ARCH 417 | autodetect_buildhost_arch 418 | 419 | # Use the project tree as the current directory 420 | PROJ_DIR=$(dirname "$0") 421 | PROJ_DIR=$(cd "$PROJ_DIR" && pwd) 422 | cd "$PROJ_DIR" 423 | 424 | # --- Tool directory selection --- 425 | 426 | tools_dir= 427 | tools_name=dktool 428 | # 1. Check if CI since many CI providers can only cache content in a subdirectory 429 | # of the project. 430 | if [ -z "$tools_dir" ] && [ "${CI:-}" = true ]; then 431 | install -d "$PROJ_DIR/.tools" 432 | tools_dir="$PROJ_DIR/.tools" 433 | fi 434 | # 2. Check in locations rooted under /opt/diskuv 435 | # We look under a /opt/diskuv early because 436 | # - Especially important for WSL2 to use a pure Linux filesystem (ext4) for 437 | # best performance. 438 | # - Using a canonical location (especially /opt/diskuv/usr/share) makes 439 | # it easy to use CMake presets for non-Windows hosts. 440 | if [ -z "$tools_dir" ] && [ -n "${XDG_DATA_HOME:-}" ] && [ -w "/opt/diskuv/$XDG_DATA_HOME" ]; then 441 | install -d "/opt/diskuv/$XDG_DATA_HOME/$tools_name" 442 | tools_dir="/opt/diskuv/$XDG_DATA_HOME/$tools_name" 443 | fi 444 | if [ -z "$tools_dir" ] && [ -n "${HOME:-}" ] && [ -w "/opt/diskuv/$HOME" ]; then 445 | install -d "/opt/diskuv/$HOME/.local/share/$tools_name" 446 | tools_dir="/opt/diskuv/$HOME/.local/share/$tools_name" 447 | fi 448 | if [ -z "$tools_dir" ] && [ -w "/opt/diskuv/usr/share" ]; then 449 | install -d "/opt/diskuv/usr/share/$tools_name" 450 | tools_dir="/opt/diskuv/usr/share/$tools_name" 451 | fi 452 | # 3. Check in the conventional locations rooted under / 453 | if [ -z "$tools_dir" ] && [ -n "${XDG_DATA_HOME:-}" ] && [ -w "$XDG_DATA_HOME" ]; then 454 | install -d "$XDG_DATA_HOME/$tools_name" 455 | tools_dir="$XDG_DATA_HOME/$tools_name" 456 | fi 457 | if [ -z "$tools_dir" ] && [ -n "${HOME:-}" ] && [ -w "$HOME" ]; then 458 | install -d "$HOME/.local/share/$tools_name" 459 | tools_dir="$HOME/.local/share/$tools_name" 460 | fi 461 | # 4. Validate 462 | if [ -z "$tools_dir" ]; then 463 | echo "FATAL: Could not find a location to install the tools necessary for this project." >&2 464 | echo " ...: Make sure you have a home directory and that it is write-able, or define" >&2 465 | echo " ...: the environment variable XDG_DATA_HOME in a shell profile script after" >&2 466 | echo " ...: creating the \$XDG_DATA_HOME directory." >&2 467 | exit 2 468 | fi 469 | 470 | # --- Tool downloads and installs --- 471 | 472 | # PREREQS 473 | # ------- 474 | 475 | install_linux_prog() { 476 | install_linux_prog_NAME=$1 477 | shift 478 | install_linux_prog_PKG=$1 479 | shift 480 | if [ -x "/usr/bin/$install_linux_prog_NAME" ]; then 481 | export DK_PROG_INSTALLED_LOCATION="/usr/bin/$install_linux_prog_NAME" 482 | else 483 | if command -v yum > /dev/null 2> /dev/null; then 484 | if [ "$(id -u)" -eq 0 ]; then 485 | yum install -y "$install_linux_prog_PKG" 486 | else 487 | echo "Running: sudo yum install -y $install_linux_prog_PKG" 488 | sudo yum install -y "$install_linux_prog_PKG" 489 | fi 490 | else 491 | if [ "$(id -u)" -eq 0 ]; then 492 | apt-get -q install -y "$install_linux_prog_PKG" 493 | else 494 | echo "Running: sudo -q apt-get -qq install -y --no-install-suggests $install_linux_prog_PKG" 495 | sudo apt-get -qq install -y --no-install-suggests "$install_linux_prog_PKG" 496 | fi 497 | fi 498 | DK_PROG_INSTALLED_LOCATION=$(command -v "$install_linux_prog_NAME") 499 | export DK_PROG_INSTALLED_LOCATION 500 | fi 501 | } 502 | 503 | get_homebrew_binary() { 504 | get_homebrew_binary_NAME=$1 505 | shift 506 | if command -v brew > /dev/null 2> /dev/null; then 507 | get_homebrew_binary_PREFIX=$(brew --prefix) 508 | if [ -x "$get_homebrew_binary_PREFIX/bin/$get_homebrew_binary_NAME" ]; then 509 | export DK_PROG_INSTALLED_LOCATION="$get_homebrew_binary_PREFIX/bin/$get_homebrew_binary_NAME" 510 | return 0 511 | fi 512 | fi 513 | return 1 514 | } 515 | 516 | install_macos_prog() { 517 | install_macos_prog_NAME=$1 518 | shift 519 | install_macos_prog_PKG=$1 520 | shift 521 | if [ -x "/usr/bin/$install_macos_prog_NAME" ]; then 522 | export DK_PROG_INSTALLED_LOCATION="/usr/bin/$install_macos_prog_NAME" 523 | elif [ -x "/usr/local/bin/$install_macos_prog_NAME" ]; then 524 | export DK_PROG_INSTALLED_LOCATION="/usr/local/bin/$install_macos_prog_NAME" 525 | elif get_homebrew_binary "$install_macos_prog_NAME"; then 526 | # DK_PROG_INSTALLED_LOCATION already set by [get_homebrew_binary] 527 | true 528 | else 529 | if command -v brew > /dev/null 2> /dev/null; then 530 | brew install --quiet --formula "$install_macos_prog_PKG" >&2 531 | get_homebrew_binary "$install_macos_prog_NAME" 532 | # DK_PROG_INSTALLED_LOCATION already set by [get_homebrew_binary] 533 | elif command -v port > /dev/null 2> /dev/null; then 534 | if [ "$(id -u)" -eq 0 ]; then 535 | port install "$install_macos_prog_PKG" 536 | else 537 | echo "Running: sudo port install $install_macos_prog_PKG" 538 | sudo port install "$install_macos_prog_PKG" 539 | fi 540 | DK_PROG_INSTALLED_LOCATION=$(command -v "$install_macos_prog_NAME") 541 | export DK_PROG_INSTALLED_LOCATION 542 | else 543 | echo "FATAL: Neither Homebrew nor MacPorts are available on your macOS. You can follow https://docs.brew.sh/Installation to install Homebrew." >&2 544 | exit 2 545 | fi 546 | fi 547 | } 548 | 549 | case $BUILDHOST_ARCH in 550 | linux_*) 551 | install_linux_prog wget wget # For [downloadfile] 552 | install_linux_prog tar tar # For handling tar balls later in this script 553 | ;; 554 | esac 555 | 556 | # NINJA 557 | # ----- 558 | # We need a valid CMAKE_GENERATOR to do FetchContent_Populate() in script mode 559 | 560 | NINJA_EXE= 561 | case $BUILDHOST_ARCH in 562 | darwin_*) 563 | install_macos_prog ninja ninja 564 | NINJA_EXE=$DK_PROG_INSTALLED_LOCATION;; 565 | linux_*) 566 | install_linux_prog ninja ninja-build 567 | NINJA_EXE=$DK_PROG_INSTALLED_LOCATION;; 568 | esac 569 | 570 | # CMAKE 571 | # ----- 572 | 573 | case $BUILDHOST_ARCH in 574 | linux_*) 575 | # This is for CMake to do FetchContent() 576 | install_linux_prog git git ;; 577 | esac 578 | case $BUILDHOST_ARCH in 579 | linux_x86) 580 | # This is for Python wheel extraction 581 | install_linux_prog unzip unzip ;; 582 | esac 583 | 584 | cmake_base= 585 | cmake_majmin_ver=3.25 586 | cmake_majminpat_ver=3.25.2 587 | cmake_bindir= 588 | cmake_destdir=$tools_dir/cmake-$cmake_majminpat_ver 589 | install -d "$tools_dir/dl" 590 | download_cmake() { 591 | case $BUILDHOST_ARCH in 592 | darwin_*) 593 | install_macos_prog cmake cmake 594 | cmake_bindir=$(dirname "$DK_PROG_INSTALLED_LOCATION") 595 | ;; 596 | linux_x86_64) 597 | cmake_base="cmake-$cmake_majminpat_ver-linux-x86_64" 598 | printf "%s\n\n" "-- Downloading cmake-$cmake_majminpat_ver for Linux x86_64" >&2 599 | downloadfile \ 600 | "https://github.com/Kitware/CMake/releases/download/v$cmake_majminpat_ver/$cmake_base.tar.gz" \ 601 | "$tools_dir/dl/cmake.tar.gz" \ 602 | 783da74f132fd1fea91b8236d267efa4df5b91c5eec1dea0a87f0cf233748d99 603 | cmake_bindir="$cmake_destdir/bin" ;; 604 | linux_x86) 605 | # CMake does not provide 32-bit binaries. But pypi does at https://pypi.org/project/cmake/ 606 | printf "%s\n\n" "-- Downloading cmake-$cmake_majminpat_ver for Linux x86" >&2 607 | downloadfile \ 608 | https://files.pythonhosted.org/packages/11/6e/aeeddf2f5b16542b6a30ceab4896421e8705d8e9a9296dba79395db11b00/cmake-3.25.2-py2.py3-none-manylinux_2_17_i686.manylinux2014_i686.whl \ 609 | "$tools_dir/dl/cmake.whl" \ 610 | 715ef82e81b48db3e4c7744614c15ff361d53f6987fd70b1b66b0880595f2e2c 611 | cmake_bindir="$cmake_destdir/bin" ;; 612 | linux_arm64) 613 | cmake_base="cmake-$cmake_majminpat_ver-linux-aarch64" 614 | printf "%s\n\n" "-- Downloading cmake-$cmake_majminpat_ver for Linux ARM64" >&2 615 | downloadfile \ 616 | "https://github.com/Kitware/CMake/releases/download/v$cmake_majminpat_ver/$cmake_base.tar.gz" \ 617 | "$tools_dir/dl/cmake.tar.gz" \ 618 | 9216ecf0449ade700e66e0def11eeaebf9fa7d4428c02f49cb59f11418d3f8a5 619 | cmake_bindir="$cmake_destdir/bin" ;; 620 | esac 621 | } 622 | export_cmake_vars() { 623 | case $BUILDHOST_ARCH in 624 | darwin_*) 625 | if command -v brew > /dev/null 2> /dev/null; then 626 | cmake_bindir=$(brew --prefix cmake)/bin 627 | else 628 | cmake_bindir=$(command -v cmake) 629 | cmake_bindir=$(dirname "$cmake_bindir") 630 | fi 631 | ;; 632 | linux_x86_64) 633 | cmake_bindir="$cmake_destdir/bin" ;; 634 | linux_x86) 635 | cmake_bindir="$cmake_destdir/bin" ;; 636 | esac 637 | } 638 | have_correct_cmake=0 639 | if [ -x "$tools_dir/cmake-$cmake_majminpat_ver/bin/cmake" ]; then 640 | # shellcheck disable=SC2016 641 | have_correct_cmake_VER=$("$tools_dir/cmake-$cmake_majminpat_ver/bin/cmake" --version | $DKMLSYS_AWK 'NR==1{print $NF}') 642 | if [ "$have_correct_cmake_VER" = "$cmake_majminpat_ver" ]; then 643 | have_correct_cmake=1 644 | fi 645 | fi 646 | if [ $have_correct_cmake -eq 0 ]; then 647 | download_cmake 648 | fi 649 | # Handle tarball 650 | if [ -e "$tools_dir/dl/cmake.tar.gz" ] && [ -n "$cmake_base" ]; then 651 | rm -rf "$tools_dir/cmake-$cmake_majminpat_ver" 652 | install -d "$cmake_destdir" 653 | tar xCfz "$cmake_destdir" "$tools_dir/dl/cmake.tar.gz" 654 | rm -f "$tools_dir/dl/cmake.tar.gz" 655 | set +f 656 | if [ -e "$cmake_destdir/$cmake_base/CMake.app" ]; then 657 | mv "$cmake_destdir/$cmake_base/CMake.app/Contents"/* "$cmake_destdir/" 658 | else 659 | mv "$cmake_destdir/$cmake_base"/* "$cmake_destdir/" 660 | fi 661 | set -f 662 | fi 663 | # Handle Python wheel 664 | if [ -e "$tools_dir/dl/cmake.whl" ]; then 665 | rm -rf "$tools_dir/cmake-$cmake_majminpat_ver" 666 | cd "$tools_dir/dl" 667 | rm -rf cmake/data 668 | # Don't want cmake/data/{aclocal,bash-completion,emacs,vim} 669 | unzip -q cmake.whl 'cmake/data/bin/**' 670 | unzip -q cmake.whl 'cmake/data/doc/**' 671 | # Don't want cmake/data/share/cmake-$cmake_majmin_ver/Help 672 | unzip -q cmake.whl "cmake/data/share/cmake-$cmake_majmin_ver/"'include/**' 673 | unzip -q cmake.whl "cmake/data/share/cmake-$cmake_majmin_ver/"'Modules/**' 674 | unzip -q cmake.whl "cmake/data/share/cmake-$cmake_majmin_ver/"'Templates/**' 675 | rm -f cmake.whl 676 | cd - 677 | set +f 678 | install -d "$cmake_destdir/share" "$cmake_destdir/doc" "$cmake_destdir/bin" 679 | mv "$tools_dir/dl/cmake/data/bin"/* "$cmake_destdir/bin/" 680 | rm -rf "$cmake_destdir/share/cmake-$cmake_majmin_ver" "$cmake_destdir/doc/cmake-$cmake_majmin_ver" "$cmake_destdir/doc/cmake" 681 | mv "$tools_dir/dl/cmake/data/share/cmake-$cmake_majmin_ver" "$cmake_destdir/share/" 682 | if [ -e "$tools_dir/dl/cmake/data/doc/cmake" ]; then # Windows wheel 683 | mv "$tools_dir/dl/cmake/data/doc/cmake" "$cmake_destdir/doc/" 684 | fi 685 | if [ -e "$tools_dir/dl/cmake/data/doc/cmake-$cmake_majmin_ver" ]; then # Linux wheel 686 | mv "$tools_dir/dl/cmake/data/doc/cmake-$cmake_majmin_ver" "$cmake_destdir/doc/" 687 | fi 688 | set -f 689 | # other dirs: aclocal bash-completion emacs vim 690 | rm -rf "$tools_dir/dl/cmake/data/share" 691 | # be pedantic. if we don't know about a directory, it may be important. so error 692 | # if some directory is non-empty 693 | rmdir "$tools_dir/dl/cmake/data/bin" "$tools_dir/dl/cmake/data/doc" 694 | rmdir "$tools_dir/dl/cmake/data" 695 | fi 696 | 697 | # Put tools in PATH 698 | export_cmake_vars 699 | if [ -n "$cmake_bindir" ] && [ -d "$cmake_bindir" ]; then 700 | tools_bin_dir=$(cd "$cmake_bindir" && pwd) 701 | export PATH="$tools_bin_dir:$PATH" 702 | else 703 | echo "This platform is not supported. No cmake 3.25+ download logic has been added" >&2 704 | exit 1 705 | fi 706 | 707 | # Validate 708 | "$cmake_bindir/cmake" --version > /dev/null 709 | 710 | # --- Run finder script --- 711 | 712 | cd "$PROJ_DIR" 713 | "$cmake_bindir/cmake" \ 714 | -D CMAKE_GENERATOR=Ninja -D CMAKE_MAKE_PROGRAM="$NINJA_EXE" \ 715 | -D "DKTOOL_PWD:FILEPATH=$dk_pwd" \ 716 | -D "DKTOOL_WORKDIR:FILEPATH=$tools_dir/work" -D "DKTOOL_CMDLINE:STRING=$*" \ 717 | -P cmake/FindDkToolScripts.cmake 718 | -------------------------------------------------------------------------------- /dk.cmd: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM ########################################################################## 4 | REM # File: dktool/dk.cmd # 5 | REM # # 6 | REM # Copyright 2023 Diskuv, Inc. # 7 | REM # # 8 | REM # Licensed under the Open Software License version 3.0 # 9 | REM # (the "License"); you may not use this file except in compliance # 10 | REM # with the License. You may obtain a copy of the License at # 11 | REM # # 12 | REM # https://opensource.org/license/osl-3-0-php/ # 13 | REM # # 14 | REM ########################################################################## 15 | 16 | REM Recommendation: Place this file in source control. 17 | REM Auto-generated by `./dk dksdk.project.new` of dktool. 18 | 19 | REM The canonical way to run this script is: ./dk 20 | REM That works in Powershell on Windows, and in Unix. Copy-and-paste works! 21 | 22 | SETLOCAL 23 | 24 | REM Coding guidelines 25 | REM 1. Microsoft way of getting around PowerShell permissions: 26 | REM https://github.com/microsoft/vcpkg/blob/71422c627264daedcbcd46f01f1ed0dcd8460f1b/bootstrap-vcpkg.bat 27 | REM 2. Write goto downward please so code flow is top to bottom. 28 | 29 | SET DK_CMAKE_VER=3.25.3 30 | SET DK_NINJA_VER=1.11.1 31 | SET DK_BUILD_TYPE=Release 32 | SET DK_SHARE=%LOCALAPPDATA%\Programs\DkSDK\dktool 33 | SET DK_PROJ_DIR=%~dp0 34 | SET DK_PWD=%CD% 35 | 36 | REM -------------- CMAKE -------------- 37 | 38 | REM Find CMAKE.EXE 39 | where.exe /q cmake.exe >NUL 2>NUL 40 | IF %ERRORLEVEL% neq 0 ( 41 | goto FindDownloadedCMake 42 | ) 43 | FOR /F "tokens=* usebackq" %%F IN (`where.exe cmake.exe`) DO ( 44 | SET "DK_CMAKE_EXE=%%F" 45 | ) 46 | 47 | REM Check if present at /cmake-VER/bin/cmake.exe 48 | :FindDownloadedCMake 49 | IF EXIST %DK_SHARE%\cmake-%DK_CMAKE_VER%-windows-x86_64\bin\cmake.exe ( 50 | SET "DK_CMAKE_EXE=%DK_SHARE%\cmake-%DK_CMAKE_VER%-windows-x86_64\bin\cmake.exe" 51 | GOTO ValidateCMake 52 | ) 53 | 54 | REM Download CMAKE.EXE 55 | bitsadmin /transfer dktool-cmake /download /priority FOREGROUND ^ 56 | "https://github.com/Kitware/CMake/releases/download/v%DK_CMAKE_VER%/cmake-%DK_CMAKE_VER%-windows-x86_64.zip" ^ 57 | "%TEMP%\cmake-%DK_CMAKE_VER%-windows-x86_64.zip" 58 | IF %ERRORLEVEL% equ 0 ( 59 | GOTO UnzipCMakeZip 60 | ) 61 | REM Try PowerShell 3+ instead 62 | powershell -NoProfile -ExecutionPolicy Bypass -Command ^ 63 | "Invoke-WebRequest https://github.com/Kitware/CMake/releases/download/v%DK_CMAKE_VER%/cmake-%DK_CMAKE_VER%-windows-x86_64.zip -OutFile '%TEMP%\cmake-%DK_CMAKE_VER%-windows-x86_64.zip'" 64 | IF %ERRORLEVEL% neq 0 ( 65 | echo. 66 | echo.Could not download CMake %DK_CMAKE_VER%. Make sure that PowerShell is installed 67 | echo.and has not been disabled by a corporate policy. 68 | echo. 69 | EXIT /b 1 70 | ) 71 | 72 | REM Unzip CMAKE.EXE (use PowerShell; could download unzip.exe and sha256sum.exe as well in case corporate policy) 73 | :UnzipCMakeZip 74 | powershell -NoProfile -ExecutionPolicy Bypass -Command ^ 75 | "Expand-Archive '%TEMP%\cmake-%DK_CMAKE_VER%-windows-x86_64.zip' -DestinationPath '%DK_SHARE%'" 76 | IF %ERRORLEVEL% neq 0 ( 77 | echo. 78 | echo.Could not unzip CMake %DK_CMAKE_VER%. Make sure that PowerShell is installed 79 | echo.and has not been disabled by a corporate policy. 80 | echo. 81 | EXIT /b 1 82 | ) 83 | SET "DK_CMAKE_EXE=%DK_SHARE%\cmake-%DK_CMAKE_VER%-windows-x86_64\bin\cmake.exe" 84 | 85 | REM Validate cmake.exe 86 | :ValidateCMake 87 | "%DK_CMAKE_EXE%" -version >NUL 2>NUL 88 | if %ERRORLEVEL% neq 0 ( 89 | echo. 90 | echo.%DK_CMAKE_EXE% 91 | echo.is not responding to the -version option. Make sure that 92 | echo.CMake is installed correctly. 93 | echo. 94 | exit /b 1 95 | ) 96 | 97 | REM -------------- NINJA -------------- 98 | 99 | REM Find NINJA.EXE 100 | where.exe /q ninja.exe >NUL 2>NUL 101 | IF %ERRORLEVEL% neq 0 ( 102 | goto FindDownloadedNinja 103 | ) 104 | FOR /F "tokens=* usebackq" %%F IN (`where.exe ninja.exe`) DO ( 105 | SET "DK_NINJA_EXE=%%F" 106 | ) 107 | 108 | REM Check if present at /ninja-VER/bin/ninja.exe 109 | :FindDownloadedNinja 110 | IF EXIST %DK_SHARE%\ninja-%DK_NINJA_VER%-windows-x86_64\bin\ninja.exe ( 111 | SET "DK_NINJA_EXE=%DK_SHARE%\ninja-%DK_NINJA_VER%-windows-x86_64\bin\ninja.exe" 112 | GOTO ValidateNinja 113 | ) 114 | 115 | REM Download NINJA.EXE 116 | bitsadmin /transfer dktool-ninja /download /priority FOREGROUND ^ 117 | "https://github.com/ninja-build/ninja/releases/download/v%DK_NINJA_VER%/ninja-win.zip" ^ 118 | "%TEMP%\ninja-%DK_NINJA_VER%-windows-x86_64.zip" 119 | IF %ERRORLEVEL% equ 0 ( 120 | GOTO UnzipNinjaZip 121 | ) 122 | REM Try PowerShell 3+ instead 123 | powershell -NoProfile -ExecutionPolicy Bypass -Command ^ 124 | "Invoke-WebRequest https://github.com/ninja-build/ninja/releases/download/v%DK_NINJA_VER%/ninja-win.zip -OutFile '%TEMP%\ninja-%DK_NINJA_VER%-windows-x86_64.zip'" 125 | IF %ERRORLEVEL% neq 0 ( 126 | echo. 127 | echo.Could not download Ninja %DK_NINJA_VER%. Make sure that PowerShell is installed 128 | echo.and has not been disabled by a corporate policy. 129 | echo. 130 | EXIT /b 1 131 | ) 132 | 133 | REM Unzip NINJA.EXE (use PowerShell; could download unzip.exe and sha256sum.exe as well in case corporate policy) 134 | :UnzipNinjaZip 135 | powershell -NoProfile -ExecutionPolicy Bypass -Command ^ 136 | "Expand-Archive '%TEMP%\ninja-%DK_NINJA_VER%-windows-x86_64.zip' -DestinationPath '%DK_SHARE%\ninja-%DK_NINJA_VER%-windows-x86_64\bin'" 137 | IF %ERRORLEVEL% neq 0 ( 138 | echo. 139 | echo.Could not unzip Ninja %DK_NINJA_VER%. Make sure that PowerShell is installed 140 | echo.and has not been disabled by a corporate policy. 141 | echo. 142 | EXIT /b 1 143 | ) 144 | SET "DK_NINJA_EXE=%DK_SHARE%\ninja-%DK_NINJA_VER%-windows-x86_64\bin\ninja.exe" 145 | 146 | REM Validate ninja.exe 147 | :ValidateNinja 148 | "%DK_NINJA_EXE%" --version >NUL 2>NUL 149 | if %ERRORLEVEL% neq 0 ( 150 | echo. 151 | echo.%DK_NINJA_EXE% 152 | echo.is not responding to the --version option. Make sure that 153 | echo.Ninja is installed correctly. 154 | echo. 155 | exit /b 1 156 | ) 157 | 158 | REM -------------- DkML PATH --------- 159 | REM We get "git-sh-setup: file not found" in Git for Windows because 160 | REM Command Prompt has the "Path" environment variable, while PowerShell 161 | REM and `with-dkml` use the PATH environment variable. Sadly both 162 | REM can be present in Command Prompt at the same time. Git for Windows 163 | REM (called by FetchContent in CMake) does not comport with what Command 164 | REM Prompt is using. So we let Command Prompt be the source of truth by 165 | REM removing any duplicated PATH twice and resetting to what Command Prompt 166 | REM thinks the PATH is. 167 | 168 | SET _DK_PATH=%PATH% 169 | SET PATH= 170 | SET PATH= 171 | SET PATH=%_DK_PATH% 172 | SET _DK_PATH= 173 | 174 | REM -------------- Run finder -------------- 175 | 176 | cd /d %DK_PROJ_DIR% 177 | "%DK_CMAKE_EXE%" -D CMAKE_GENERATOR=Ninja -D "CMAKE_MAKE_PROGRAM=%DK_NINJA_EXE%" -D "DKTOOL_PWD:FILEPATH=%DK_PWD%" -D "DKTOOL_WORKDIR:FILEPATH=%DK_SHARE%\work" -D "DKTOOL_CMDLINE:STRING=%*" -P cmake/FindDkToolScripts.cmake 178 | -------------------------------------------------------------------------------- /dune: -------------------------------------------------------------------------------- 1 | ; DO NOT ... DO NOT ... DO NOT ... update the README.md file automatically 2 | ; as part of the build. We do not want upgrades to `cmdliner` to break 3 | ; anybody's build! 4 | 5 | ; Use the following to update the docs ... 6 | ; Windows: with-dkml dune build `@runmarkdown --auto-promote 7 | ; Unix: dune build @runmarkdown --auto-promote 8 | 9 | ; This is so that MDX can run `diskuvbox` directly. 10 | 11 | (rule 12 | (target diskuvbox.exe) 13 | (action 14 | (copy src/bin/main.exe %{target}))) 15 | 16 | ; BEGIN HACK - Workaround unsupported ```console blocks 17 | 18 | (rule 19 | (alias runmarkdown) 20 | (deps 21 | (:in README.md) 22 | diskuvbox.exe 23 | src/lib/dune.runlicense.inc) 24 | (action 25 | (progn 26 | (run ./mdx-console.sh %{in}) 27 | (no-infer 28 | (diff README.md README.md.corrected))))) 29 | 30 | ; END HACK 31 | 32 | ; Use the following to update the docs ... 33 | ; Windows: with-dkml CORRECT_MARKDOWN=true dune runtest --auto-promote 34 | ; Unix: env CORRECT_MARKDOWN=true dune runtest --auto-promote 35 | 36 | ; BEGIN EXPECTED 37 | ; (mdx 38 | ; (deps diskuvbox.exe src/lib/dune.runlicense.inc) ; only available in Dune 3.0+ 39 | ; (enabled_if %{env:CORRECT_MARKDOWN=false}) 40 | ; (files README.md)) 41 | ; END EXPECTED 42 | -------------------------------------------------------------------------------- /dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 2.9) 2 | 3 | (name diskuvbox) 4 | (version 0.2.0) 5 | (cram enable) 6 | (using mdx 0.1) 7 | 8 | (generate_opam_files true) 9 | 10 | (source 11 | (github diskuv/diskuvbox)) 12 | (license Apache-2.0) 13 | (authors "Diskuv, Inc. ") 14 | (maintainers "opensource+diskuv-ocaml@support.diskuv.com") 15 | (documentation https://diskuv.github.io/diskuvbox/diskuvbox/index.html) 16 | 17 | (package 18 | (name diskuvbox) 19 | (synopsis "Cross-platform basic set of script commands") 20 | (description 21 | "A cross-platform basic set of script commands. Available as a single binary (`diskuvbox`, or `diskuvbox.exe` on Windows) and as an OCaml library.") 22 | (depends 23 | (odoc (and (>= 1.5.3) :with-doc)) 24 | ; minimums come from matrix testing in .github/workflows/test.yml 25 | (ocaml (>= 4.10.0)) 26 | ; all of these may go lower; simply has not been tested 27 | (ppx_deriving (>= 5.2.1)) 28 | (bos (>= 0.2.0)) 29 | (fmt (>= 0.8.9)) 30 | (logs (>= 0.7.0)) 31 | (result (>= 1.5)) 32 | (mdx (and (>= 2.0.0) :with-test)) 33 | (cmdliner (>= 1.1.0)) 34 | )) 35 | 36 | (package 37 | (name diskuvbox-maintain) 38 | (synopsis "Maintenance for diskuvbox: formatting, licenses, embedded docs") 39 | (description 40 | "Maintenance tools that minimize the dependencies for diskuvbox itself.") 41 | (depends 42 | (diskuvbox (= :version)) 43 | (dkml-workflows (>= 1.1.0)) 44 | ; these are used in src/lib/dune.runlicense.inc 45 | (headache (>= 1.05)) 46 | (ocamlformat (= 0.19.0)) 47 | )) 48 | -------------------------------------------------------------------------------- /etc/headache.conf: -------------------------------------------------------------------------------- 1 | # Objective Caml source 2 | ".*\\.ml[il]?" -> frame open:"(*" line:"*" close:"*)" 3 | | ".*\\.fml[i]?" -> frame open:"(*" line:"*" close:"*)" 4 | | ".*\\.mly" -> frame open:"/*" line:"*" close:"*/" 5 | # C source 6 | | ".*\\.[chy]" -> frame open:"/*" line:"*" close:"*/" 7 | # Latex 8 | | ".*\\.tex" -> frame open:"%" line:"%" close:"%" 9 | # Misc 10 | | ".*Makefile.*" -> frame open:"#" line:"#" close:"#" 11 | | ".*LICENSE.*" -> frame open:"*" line:"*" close:"*" 12 | | "dune" -> frame open:";" line:";" close:";" -------------------------------------------------------------------------------- /etc/license-header.txt: -------------------------------------------------------------------------------- 1 | Copyright 2022 Diskuv, Inc. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /mdx-console.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | IN=$1 3 | shift 4 | 5 | # shellcheck disable=SC2016 6 | sed 's/```console/```sh/g; s#[$] diskuvbox#$ ./diskuvbox.exe#g' "$IN" > "$IN.sh" 7 | 8 | # produce "$IN.sh".corrected 9 | TERM=dumb ocaml-mdx test --force-output "$IN.sh" 10 | 11 | # shellcheck disable=SC2016 12 | sed 's/```sh/```console/g; s#[$] ./diskuvbox.exe#$ diskuvbox#g' "$IN.sh.corrected" > "$IN.corrected" 13 | -------------------------------------------------------------------------------- /src/bin/diskuvbox.dlls.txt: -------------------------------------------------------------------------------- 1 | ; Only stublib DLLs that are part of a standard OCaml installation should be present below. 2 | ; dllunix and others are always built in official OCaml packages like https://archlinux.org/packages/extra/x86_64/ocaml/files/ 3 | ; (search for usr/lib/ocaml/stublibs/) 4 | ; The authority is OCaml's source code at https://github.com/ocaml/ocaml/tree/trunk/otherlibs 5 | 6 | Used DLLs: 7 | dllunix 8 | -------------------------------------------------------------------------------- /src/bin/dune: -------------------------------------------------------------------------------- 1 | (executable 2 | (package diskuvbox) 3 | (name main) 4 | (public_name diskuvbox) 5 | (modes byte exe) 6 | (libraries diskuvbox cmdliner fmt.cli fmt.tty logs.cli logs.fmt)) 7 | 8 | (rule 9 | (targets main.corrected.ml log_config.corrected.ml) 10 | (deps 11 | (:license %{project_root}/etc/license-header.txt) 12 | (:conf %{project_root}/etc/headache.conf)) 13 | (action 14 | (progn 15 | (run diskuvbox copy-file -m 644 main.ml main.corrected.ml) 16 | (run diskuvbox copy-file -m 644 log_config.ml log_config.corrected.ml) 17 | (run headache -h %{license} -c %{conf} %{targets}) 18 | (run 19 | ocamlformat 20 | --inplace 21 | --disable-conf-files 22 | --enable-outside-detected-project 23 | %{targets})))) 24 | 25 | (rule 26 | (alias runlicense) 27 | (action 28 | (diff main.ml main.corrected.ml))) 29 | 30 | (rule 31 | (alias runlicense) 32 | (action 33 | (diff log_config.ml log_config.corrected.ml))) 34 | 35 | ; Validate that diskuvbox does not contain stublibs in excess of Stdlib 36 | 37 | (rule 38 | (alias runtest) 39 | (target diskuvbox.dlls.txt.corrected) 40 | (deps 41 | (:bc main.bc)) 42 | (action 43 | (progn 44 | (with-stdout-to 45 | %{target}.info 46 | (run ocamlobjinfo %{bc})) 47 | (with-stdout-to 48 | %{target} 49 | (progn 50 | (echo 51 | "; Only stublib DLLs that are part of a standard OCaml installation should be present below.\n") 52 | (echo 53 | "; dllunix and others are always built in official OCaml packages like https://archlinux.org/packages/extra/x86_64/ocaml/files/\n") 54 | (echo "; (search for usr/lib/ocaml/stublibs/)\n") 55 | (echo 56 | "; The authority is OCaml's source code at https://github.com/ocaml/ocaml/tree/trunk/otherlibs\n\n") 57 | (run awk "/.*:/ {x=0} /Used DLLs:/{x=1} x==1 {print}" %{target}.info))) 58 | (diff diskuvbox.dlls.txt %{target})))) 59 | -------------------------------------------------------------------------------- /src/bin/log_config.ml: -------------------------------------------------------------------------------- 1 | (******************************************************************************) 2 | (* Copyright 2022 Diskuv, Inc. *) 3 | (* *) 4 | (* Licensed under the Apache License, Version 2.0 (the "License"); *) 5 | (* you may not use this file except in compliance with the License. *) 6 | (* You may obtain a copy of the License at *) 7 | (* *) 8 | (* http://www.apache.org/licenses/LICENSE-2.0 *) 9 | (* *) 10 | (* Unless required by applicable law or agreed to in writing, software *) 11 | (* distributed under the License is distributed on an "AS IS" BASIS, *) 12 | (* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *) 13 | (* See the License for the specific language governing permissions and *) 14 | (* limitations under the License. *) 15 | (******************************************************************************) 16 | 17 | type t = { 18 | log_config_style_renderer : Fmt.style_renderer option; 19 | log_config_level : Logs.level option; 20 | } 21 | (** the type of log configuration *) 22 | 23 | (** [create ?log_config_style_renderer ?log_config_level ()] creates log configuration *) 24 | let create ?log_config_style_renderer ?log_config_level () = 25 | { log_config_style_renderer; log_config_level } 26 | 27 | (** [to_args] translates the configuration to {!Bos.Cmd.t} *) 28 | let to_args { log_config_style_renderer; log_config_level } = 29 | let color = 30 | match log_config_style_renderer with 31 | | None -> "auto" 32 | | Some `None -> "never" 33 | | Some `Ansi_tty -> "always" 34 | in 35 | Bos.Cmd.( 36 | empty 37 | % ("--verbosity=" ^ Logs.level_to_string log_config_level) 38 | % ("--color=" ^ color)) 39 | -------------------------------------------------------------------------------- /src/bin/main.ml: -------------------------------------------------------------------------------- 1 | (******************************************************************************) 2 | (* Copyright 2022 Diskuv, Inc. *) 3 | (* *) 4 | (* Licensed under the Apache License, Version 2.0 (the "License"); *) 5 | (* you may not use this file except in compliance with the License. *) 6 | (* You may obtain a copy of the License at *) 7 | (* *) 8 | (* http://www.apache.org/licenses/LICENSE-2.0 *) 9 | (* *) 10 | (* Unless required by applicable law or agreed to in writing, software *) 11 | (* distributed under the License is distributed on an "AS IS" BASIS, *) 12 | (* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *) 13 | (* See the License for the specific language governing permissions and *) 14 | (* limitations under the License. *) 15 | (******************************************************************************) 16 | 17 | module Arg = Cmdliner.Arg 18 | module Cmd = Cmdliner.Cmd 19 | module Manpage = Cmdliner.Manpage 20 | module Term = Cmdliner.Term 21 | 22 | (* Error handling *) 23 | 24 | let fail_if_error = function 25 | | Ok v -> v 26 | | Error msg -> ( 27 | Logs.err (fun l -> l "FATAL: %s" msg); 28 | (* print stack trace if Debug or Info *) 29 | match Logs.level () with 30 | | Some Debug | Some Info -> failwith msg 31 | | _ -> exit 1) 32 | 33 | let box_err s = fail_if_error (Error s) 34 | 35 | (* Help sections common to all commands *) 36 | 37 | let help_secs = 38 | [ 39 | `S Manpage.s_common_options; 40 | `P "These options are common to all commands."; 41 | `S "MORE HELP"; 42 | `P "Use `$(mname) $(i,COMMAND) --help' for help on a single command."; 43 | `S Manpage.s_bugs; 44 | `P "Check bug reports at https://github.com/diskuv/diskuvbox/issues"; 45 | ] 46 | 47 | (* Options common to all commands *) 48 | 49 | let setup_log style_renderer level = 50 | Fmt_tty.setup_std_outputs ?style_renderer (); 51 | Logs.set_level level; 52 | Logs.set_reporter (Logs_fmt.reporter ()); 53 | Log_config.create ?log_config_style_renderer:style_renderer 54 | ?log_config_level:level () 55 | 56 | let copts_t = 57 | Term.(const setup_log $ Fmt_cli.style_renderer () $ Logs_cli.level ()) 58 | 59 | (* Commands *) 60 | 61 | let source_dirs_t ~verb = 62 | let doc = 63 | Fmt.str 64 | "One or more source directories %s. The command fails when a $(docv) \ 65 | does not exist." 66 | verb 67 | in 68 | let stringdirlist_t = 69 | Arg.(non_empty & pos_left ~rev:true 0 dir [] & info [] ~doc ~docv:"SRCDIR") 70 | in 71 | Term.(const (List.map Fpath.v) $ stringdirlist_t) 72 | 73 | let source_files_t ~verb = 74 | let doc = 75 | Fmt.str 76 | "One or more source files %s. The command fails when a $(docv) does not \ 77 | exist." 78 | verb 79 | in 80 | let stringfilelist_t = 81 | Arg.( 82 | non_empty & pos_left ~rev:true 0 file [] & info [] ~doc ~docv:"SRCFILE") 83 | in 84 | Term.(const (List.map Fpath.v) $ stringfilelist_t) 85 | 86 | let touch_files_t = 87 | let doc = 88 | Fmt.str 89 | "One or more files to touch. If a $(docv) does not exist it will be \ 90 | created." 91 | in 92 | let filelist_t = 93 | Arg.(non_empty & pos_all string [] & info [] ~doc ~docv:"FILE") 94 | in 95 | Term.(const (List.map Fpath.v) $ filelist_t) 96 | 97 | let basenames_t = 98 | let doc = 99 | Fmt.str 100 | "One or more basenames to search. The command fails when a $(docv) is \ 101 | blank or has a directory separator." 102 | in 103 | let stringfilelist_t = 104 | Arg.(non_empty & pos_right 0 string [] & info [] ~doc ~docv:"BASENAME") 105 | in 106 | Term.(const (List.map Fpath.v) $ stringfilelist_t) 107 | 108 | let source_file_t ~verb = 109 | let doc = 110 | Fmt.str 111 | "The source file %s. The command fails when a $(docv) does not exist." 112 | verb 113 | in 114 | let stringfile_t = 115 | Arg.(required & pos 0 (some file) None & info [] ~doc ~docv:"SRCFILE") 116 | in 117 | Term.(const Fpath.v $ stringfile_t) 118 | 119 | let dest_dir_t = 120 | let doc = 121 | "Destination directory. If $(docv) does not exist it will be created." 122 | in 123 | let stringdir_t = 124 | Arg.( 125 | required 126 | & pos ~rev:true 0 (some string) None 127 | & info [] ~doc ~docv:"DESTDIR") 128 | in 129 | Term.(const Fpath.v $ stringdir_t) 130 | 131 | let dest_file_t = 132 | let doc = 133 | Fmt.str "Destination file. If $(docv) does not exist it will be created." 134 | in 135 | let stringfile_t = 136 | Arg.(required & pos 1 (some string) None & info [] ~doc ~docv:"DESTFILE") 137 | in 138 | Term.(const Fpath.v $ stringfile_t) 139 | 140 | let dir_t ~verb ~docv = 141 | let doc = 142 | Fmt.str "Directory %s. The command fails when $(docv) does not exist." verb 143 | in 144 | let stringfile_t = 145 | Arg.(required & pos 0 (some dir) None & info [] ~doc ~docv) 146 | in 147 | Term.(const Fpath.v $ stringfile_t) 148 | 149 | let path_printer_t = 150 | let doc = 151 | Fmt.str 152 | "Print files and directories in native format. On Windows the native \ 153 | format uses backslashes as directory separators, while on Unix \ 154 | (including macOS) the native format uses forward slashes. If $(opt) is \ 155 | not specified then all files and directories are printed with the \ 156 | directory separators as forward slashes." 157 | in 158 | let native_t = Arg.(value & flag & info [ "native" ] ~doc) in 159 | let path_printer native = 160 | if native then Fpath.pp 161 | else fun fmt path -> 162 | Format.pp_print_string fmt 163 | (let s = Fmt.str "%a" Fpath.pp path in 164 | String.map (function '\\' -> '/' | c -> c) s) 165 | in 166 | Term.(const path_printer $ native_t) 167 | 168 | let chmod_mode_opt_t = 169 | let doc = 170 | "The chmod mode permission of the destination file, in octal. If not \ 171 | specified then the chmod mode permission of the source file is used. \ 172 | Examples: 644, 755." 173 | in 174 | let modestring_opt_t = 175 | Arg.(value & opt (some string) None & info [ "m"; "mode" ] ~doc) 176 | in 177 | let from_octal s_opt = 178 | match s_opt with Some s -> int_of_string_opt ("0o" ^ s) | None -> None 179 | in 180 | Term.(const from_octal $ modestring_opt_t) 181 | 182 | let affix_t ~affix ~verb = 183 | let doc = 184 | Printf.sprintf "A %s that will be %s to each destination file." affix verb 185 | in 186 | let t = Arg.(value & opt string "" & info [ affix ] ~doc) in 187 | let valid_basename_or_empty = function 188 | | "" -> "" 189 | | s -> 190 | (* Validate that the affix is a valid file pathname *) 191 | let fp = 192 | fail_if_error 193 | (Fpath.of_string s |> Result.map_error (function `Msg m -> m)) 194 | in 195 | let base_fp = Fpath.basename fp in 196 | fail_if_error 197 | (if String.equal s base_fp then Ok base_fp 198 | else 199 | Error 200 | (Printf.sprintf 201 | "The %s '%s' is not a valid portion of a filename. You cannot \ 202 | use directories, drive letters or anything else that does \ 203 | not belong as the basename of a filepath" 204 | affix s)) 205 | in 206 | Term.(const valid_basename_or_empty $ t) 207 | 208 | let prefix_t = affix_t ~affix:"prefix" ~verb:"prepended" 209 | let suffix_t = affix_t ~affix:"suffix" ~verb:"appended" 210 | let affix_rewriter ~prefix ~suffix s = prefix ^ s ^ suffix 211 | 212 | let copy_file_cmd = 213 | let doc = "Copy a source file to a destination file." in 214 | let man = 215 | [ 216 | `S Manpage.s_description; 217 | `P 218 | "Copy the SRCFILE to the DESTFILE. $(b,copy-file) will follow symlinks."; 219 | ] 220 | in 221 | let copy_file (_ : Log_config.t) src dst chmod_mode_opt prefix suffix = 222 | let basename_rewriter = affix_rewriter ~prefix ~suffix in 223 | fail_if_error 224 | (Diskuvbox.copy_file ~err:box_err ?mode:chmod_mode_opt ~basename_rewriter 225 | ~src ~dst ()) 226 | in 227 | Cmd.v 228 | (Cmd.info "copy-file" ~doc ~man) 229 | Term.( 230 | const copy_file $ copts_t 231 | $ source_file_t ~verb:"to copy" 232 | $ dest_file_t $ chmod_mode_opt_t $ prefix_t $ suffix_t) 233 | 234 | let copy_file_into_cmd = 235 | let doc = "Copy one or more files into a destination directory." in 236 | let man = 237 | [ 238 | `S Manpage.s_description; 239 | `P 240 | "Copy one or more SRCFILE... files to the DESTDIR directory. \ 241 | $(b,copy-files-into) will follow symlinks."; 242 | ] 243 | in 244 | let copy_file_into (_ : Log_config.t) source_files dest_dir chmod_mode_opt 245 | prefix suffix = 246 | let basename_rewriter = affix_rewriter ~prefix ~suffix in 247 | List.iter 248 | (fun source_file -> 249 | let dst = Fpath.(dest_dir / basename source_file) in 250 | fail_if_error 251 | (Diskuvbox.copy_file ~err:box_err ?mode:chmod_mode_opt 252 | ~basename_rewriter ~src:source_file ~dst ())) 253 | source_files 254 | in 255 | Cmd.v 256 | (Cmd.info "copy-file-into" ~doc ~man) 257 | Term.( 258 | const copy_file_into $ copts_t 259 | $ source_files_t ~verb:"to copy" 260 | $ dest_dir_t $ chmod_mode_opt_t $ prefix_t $ suffix_t) 261 | 262 | let copy_dir_cmd = 263 | let doc = 264 | "Copy content of one or more source directories to a destination directory." 265 | in 266 | let man = 267 | [ 268 | `S Manpage.s_description; 269 | `P 270 | "Copy content of one or more SRCDIR... directories to the DESTDIR \ 271 | directory. $(b,copy-dir) will follow symlinks."; 272 | ] 273 | in 274 | let copy_dir (_ : Log_config.t) source_dirs dest_dir prefix suffix = 275 | let basename_rewriter = affix_rewriter ~prefix ~suffix in 276 | List.iter 277 | (fun source_dir -> 278 | fail_if_error 279 | (Diskuvbox.copy_dir ~err:box_err ~basename_rewriter ~src:source_dir 280 | ~dst:dest_dir ())) 281 | source_dirs 282 | in 283 | Cmd.v 284 | (Cmd.info "copy-dir" ~doc ~man) 285 | Term.( 286 | const copy_dir $ copts_t 287 | $ source_dirs_t ~verb:"to copy" 288 | $ dest_dir_t $ prefix_t $ suffix_t) 289 | 290 | let touch_file_cmd = 291 | let doc = "Touch one or more files." in 292 | let man = 293 | [ `S Manpage.s_description; `P "Touch one or more FILE... files." ] 294 | in 295 | let touch_file (_ : Log_config.t) files = 296 | List.iter 297 | (fun file -> fail_if_error (Diskuvbox.touch_file ~err:box_err ~file ())) 298 | files 299 | in 300 | Cmd.v 301 | (Cmd.info "touch-file" ~doc ~man) 302 | Term.(const touch_file $ copts_t $ touch_files_t) 303 | 304 | let find_up_cmd = 305 | let doc = "Find a file in the current directory or one of its ancestors." in 306 | let man = 307 | [ 308 | `S Manpage.s_description; 309 | `P 310 | "Find a file that matches the name as one or more specified FILE... \ 311 | files in the FROMDIR directory."; 312 | `P "Will print the matching file if found. Otherwise will print nothing."; 313 | ] 314 | in 315 | let find_up (_ : Log_config.t) from_dir basenames path_printer = 316 | let result = 317 | fail_if_error (Diskuvbox.find_up ~err:box_err ~from_dir ~basenames ()) 318 | in 319 | match result with 320 | | Some path -> print_endline (Fmt.str "%a" path_printer path) 321 | | None -> () 322 | in 323 | Cmd.v 324 | (Cmd.info "find-up" ~doc ~man) 325 | Term.( 326 | const find_up $ copts_t 327 | $ dir_t ~verb:"to search" ~docv:"FROMDIR" 328 | $ basenames_t $ path_printer_t) 329 | 330 | let max_depth_opt = "max-depth" 331 | 332 | let max_depth_t = 333 | let doc = 334 | "Maximum depth to print. A maximum depth of 0 will never print deeper than \ 335 | the name of the starting directory. A maximum depth of 1 will, at most, \ 336 | print the contents of the starting directory. Defaults to 0" 337 | in 338 | Arg.(value & opt int 0 & info [ "d"; max_depth_opt ] ~doc) 339 | 340 | type charsets = Ascii | Utf8 341 | 342 | type print_char_pairs = { 343 | down : string; 344 | down_halfright : string; 345 | halfdown_halfright : string; 346 | right : string; 347 | halfright : string; 348 | } 349 | 350 | type encoding = { print_char_pairs : print_char_pairs } 351 | 352 | let encoding_t = 353 | let l = [ ("ASCII", Ascii); ("UTF-8", Utf8) ] in 354 | let doc = 355 | Fmt.str 356 | "The encoding of the graphic characters printed: %a. Defaults to ASCII" 357 | Fmt.(list ~sep:comma (pair ~sep:nop string nop)) 358 | l 359 | in 360 | let v = Arg.(value & opt (enum l) Ascii & info [ "e"; "encoding" ] ~doc) in 361 | let f = function 362 | | Ascii -> 363 | { 364 | print_char_pairs = 365 | { 366 | down = "| "; 367 | down_halfright = "|-"; 368 | halfdown_halfright = "`-"; 369 | right = "--"; 370 | halfright = "- "; 371 | }; 372 | } 373 | | Utf8 -> 374 | { 375 | print_char_pairs = 376 | { 377 | down = "│ "; 378 | down_halfright = "├─"; 379 | halfdown_halfright = "└─"; 380 | right = "──"; 381 | halfright = "─ "; 382 | }; 383 | } 384 | in 385 | Term.(const f $ v) 386 | 387 | let tree_cmd = 388 | let doc = "Print a directory tree." in 389 | let man = 390 | [ 391 | `S Manpage.s_description; 392 | `P 393 | (Fmt.str 394 | "Print the directory tree starting at the DIR directory. By default \ 395 | only the DIR directory (the first level) is printed. Use --%s to \ 396 | print deeper" 397 | max_depth_opt); 398 | ] 399 | in 400 | let tree (_ : Log_config.t) dir max_depth path_printer { print_char_pairs } = 401 | let _padding d = String.make d ' ' in 402 | let entry_pp fmt = function 403 | | Diskuvbox.Directory relpath -> 404 | Fmt.pf fmt "%a/" path_printer (Fpath.base relpath) 405 | | File relpath -> Fmt.pf fmt "%a" path_printer (Fpath.base relpath) 406 | | Root -> failwith "Should never have entry_pp on a Root" 407 | in 408 | let dirs_finished = Array.make max_depth false in 409 | let veins ~last depth = 410 | if depth <= 0 then [||] 411 | else 412 | let char_pairs = Array.make (depth * 2) " " in 413 | (* set all but the last 2 pairs of characters *) 414 | if depth >= 2 then 415 | for d_i = 0 to depth - 2 do 416 | if not dirs_finished.(d_i) then 417 | Array.set char_pairs (d_i * 2) print_char_pairs.down 418 | done; 419 | (* set the 2nd last pair of characters *) 420 | Array.set char_pairs 421 | ((depth * 2) - 2) 422 | (if last then print_char_pairs.halfdown_halfright 423 | else if dirs_finished.(depth - 1) then print_char_pairs.right 424 | else print_char_pairs.down_halfright); 425 | (* set the last pair of characters *) 426 | Array.set char_pairs ((depth * 2) - 1) print_char_pairs.halfright; 427 | char_pairs 428 | in 429 | let veins_pp = Fmt.(array ~sep:nop string) in 430 | let f ~depth ~path_attributes walk_path = 431 | let open Diskuvbox in 432 | match 433 | (depth, Path_attributes.mem Last_in_directory path_attributes, walk_path) 434 | with 435 | | 0, _, _ -> 436 | print_endline @@ Fmt.str "%a" path_printer dir; 437 | Ok () 438 | | _, false, _ -> 439 | Array.set dirs_finished (depth - 1) false; 440 | print_endline 441 | @@ Fmt.str "%a%a" veins_pp (veins ~last:false depth) entry_pp 442 | walk_path; 443 | Ok () 444 | | _, true, _ -> 445 | print_endline 446 | @@ Fmt.str "%a%a" veins_pp (veins ~last:true depth) entry_pp walk_path; 447 | Array.set dirs_finished (depth - 1) true; 448 | Ok () 449 | in 450 | fail_if_error 451 | (Diskuvbox.walk_down ~err:box_err ~max_depth ~from_path:dir ~f ()) 452 | in 453 | Cmd.v 454 | (Cmd.info "tree" ~doc ~man) 455 | Term.( 456 | const tree $ copts_t 457 | $ dir_t ~verb:"to print" ~docv:"DIR" 458 | $ max_depth_t $ path_printer_t $ encoding_t) 459 | 460 | let help_cmd = 461 | let doc = "display help about diskuvbox and diskuvbox commands" in 462 | let help (_ : Log_config.t) = `Help (`Pager, None) in 463 | let man = 464 | [ 465 | `S Manpage.s_description; 466 | `P "Prints help about diskuvbox commands and other subjects..."; 467 | `Blocks help_secs; 468 | ] 469 | in 470 | Cmd.v (Cmd.info "help" ~doc ~man) Term.(ret (const help $ copts_t)) 471 | 472 | let default_cmd = 473 | Term.(ret (const (fun (_ : Log_config.t) -> `Help (`Pager, None)) $ copts_t)) 474 | 475 | let cmds = 476 | [ 477 | copy_dir_cmd; 478 | copy_file_cmd; 479 | copy_file_into_cmd; 480 | touch_file_cmd; 481 | find_up_cmd; 482 | tree_cmd; 483 | help_cmd; 484 | ] 485 | 486 | let () = 487 | let doc = "a box of utilities" in 488 | let info = 489 | Cmd.info "diskuvbox" ~version:"%%VERSION%%" ~doc 490 | ~sdocs:Manpage.s_common_options 491 | in 492 | exit (Cmd.eval (Cmd.group ~default:default_cmd info cmds)) 493 | -------------------------------------------------------------------------------- /src/bin/tests/copy-dir.t: -------------------------------------------------------------------------------- 1 | Create a few files in src1/, including a subdirectory 2 | $ install -d src1 3 | $ touch src1/a src1/b src1/c 4 | $ install -d src1/s 5 | $ touch src1/s/t 6 | 7 | Create a few files in src2/, including one dotfile which is sometimes hidden 8 | $ install -d src2 9 | $ touch src2/x src2/y src2/z src2/.dotfile 10 | 11 | Create empty directory src3/ 12 | $ install -d src3 13 | 14 | Create directory src4/ with a symlink 15 | $ install -d src4 16 | $ touch src4/m 17 | $ ln -s m src4/n 18 | 19 | Use diskuvbox to copy them. The destination directory should be autocreated. 20 | $ ./diskuvbox.exe copy-dir src1 src2 src3 src4 dest 21 | 22 | Verify 23 | $ ls dest | sort 24 | a 25 | b 26 | c 27 | m 28 | n 29 | s 30 | x 31 | y 32 | z 33 | $ ls dest/s | sort 34 | t 35 | -------------------------------------------------------------------------------- /src/bin/tests/copy-file-into.t: -------------------------------------------------------------------------------- 1 | Create a file 2 | $ touch a 3 | 4 | Create a symlink 5 | $ touch m 6 | $ ln -s m n 7 | 8 | Use diskuvbox to copy them. The destination directory should be autocreated. 9 | $ ./diskuvbox.exe copy-file-into a n dest/1/2/3 10 | 11 | Verify 12 | $ ls dest/1/2/3 | sort 13 | a 14 | n 15 | -------------------------------------------------------------------------------- /src/bin/tests/copy-file-into_prefix.t: -------------------------------------------------------------------------------- 1 | Create files 2 | $ touch a m 3 | 4 | Use diskuvbox to copy them. The destination directory should be autocreated. 5 | $ ./diskuvbox.exe copy-file-into --prefix rewrite- a m dest/1/2/3 6 | 7 | Verify 8 | $ ls dest/1/2/3 | sort 9 | rewrite-a 10 | rewrite-m 11 | -------------------------------------------------------------------------------- /src/bin/tests/copy-file.t: -------------------------------------------------------------------------------- 1 | Create a file 2 | $ touch a 3 | 4 | Create a symlink 5 | $ touch m 6 | $ ln -s m n 7 | 8 | Use diskuvbox to copy each one at a time. The destination directory should be autocreated. 9 | $ ./diskuvbox.exe copy-file a dest/1/2/a_copied 10 | $ ./diskuvbox.exe copy-file n dest/1/2/n_copied 11 | 12 | Verify 13 | $ ls dest/1/2 | sort 14 | a_copied 15 | n_copied 16 | 17 | Regression test for 32-bit. 18 | | https://github.com/diskuv/diskuvbox/issues/1 19 | | Bug with 32-bit Windows: 20 | | FATAL: read D:\.opam\dkml\share\dkml-installer-network-ocaml\t\u-unsigned-diskuv-ocaml-windows_x86_64-0.4.1.exe: file too large (44.2MB, max supported size: 16.8MB) 21 | | Root cause: 22 | | * copy-file used to read file into memory. 23 | | * On 32-bit OCaml, max memory block is 2^22 words = 2^22 * 4 B = 16MB. 24 | | * Confer: https://github.com/ocaml/ocaml/blob/f40bc2697234e075eb69294e2e2e19a790de8aba/runtime/caml/mlvalues.h#L159 25 | | * Confer: https://ocamlverse.github.io/content/runtime.html 26 | $ if command -v truncate >/dev/null 2>/dev/null; then truncate -s 20MB test32bit; else dd if=/dev/zero of=test32bit bs=1024 count=0 seek=20480 2>/dev/null; fi 27 | $ ./diskuvbox.exe copy-file test32bit dest/1/2/test32bit 28 | -------------------------------------------------------------------------------- /src/bin/tests/dune: -------------------------------------------------------------------------------- 1 | (rule 2 | (target diskuvbox.exe) 3 | (action 4 | (copy ../main.exe %{target}))) 5 | 6 | (cram 7 | (deps ./diskuvbox.exe)) 8 | -------------------------------------------------------------------------------- /src/bin/tests/find-up.t: -------------------------------------------------------------------------------- 1 | Use diskuvbox to find a nonexistent file 2 | $ ./diskuvbox.exe find-up . 09535a27-1559-492e-b674-b1a680623190.does.not.exist 3 | 4 | Create a directory structure 5 | $ install -d a/b/c/d/e/f 6 | $ ./diskuvbox.exe touch a/b/i-am-here 7 | $ ./diskuvbox.exe touch a/b/c/i-am-also-here 8 | $ ./diskuvbox.exe touch a/b/c/d/something-that-wont-be-searched 9 | 10 | Use diskuvbox to find i-am-here 11 | $ ./diskuvbox.exe find-up a/b/c/d/e/f i-am-here 12 | a/b/i-am-here 13 | 14 | Use diskuvbox to find i-am-here or i-am-also-here 15 | $ ./diskuvbox.exe find-up a/b/c/d/e/f i-am-here i-am-also-here 16 | a/b/c/i-am-also-here 17 | 18 | Use diskuvbox to find .you-better-find-me in the same directory 19 | $ ./diskuvbox.exe touch .you-better-find-me 20 | $ ./diskuvbox.exe find-up . .you-better-find-me 21 | ./.you-better-find-me 22 | 23 | Use diskuvbox to find .and-this-one-too in a child directory 24 | $ install -d z 25 | $ ./diskuvbox.exe touch z/.and-this-one-too 26 | $ ./diskuvbox.exe find-up z .and-this-one-too 27 | z/.and-this-one-too 28 | -------------------------------------------------------------------------------- /src/bin/tests/touch-file.t: -------------------------------------------------------------------------------- 1 | Create non-empty files. 2 | | Since modification timestamps are hard to test cross-platform (portably), we 3 | | only do it if BOX_TIMESTAMP_TESTS=true 4 | $ echo hello > t_created_first 5 | $ if [ "$BOX_TIMESTAMP_TESTS" = true ]; then sleep 0.1; fi 6 | $ echo hello > t_created_later 7 | $ if [ "$BOX_TIMESTAMP_TESTS" != true ] || [ t_created_later -nt t_created_first ]; then echo GOOD; else echo BAD; fi 8 | GOOD 9 | 10 | Use diskuvbox to create files 11 | $ ./diskuvbox.exe touch-file a b c d/e f/g/h t_created_first 12 | 13 | Verify new files were created 14 | $ ls a b c t_created_first t_created_later 15 | a 16 | b 17 | c 18 | t_created_first 19 | t_created_later 20 | $ ls d | sort 21 | e 22 | $ ls f | sort 23 | g 24 | $ ls f/g | sort 25 | h 26 | 27 | Verify that the pre-existing touched file still has the same contents 28 | $ cat t_created_first 29 | hello 30 | 31 | Verify that the pre-existing touched file has timestamp newer than a file created later 32 | $ if [ "$BOX_TIMESTAMP_TESTS" != true ] || [ t_created_first -nt t_created_later ]; then echo GOOD; else echo BAD; fi 33 | GOOD 34 | -------------------------------------------------------------------------------- /src/bin/tests/tree-README-example.t: -------------------------------------------------------------------------------- 1 | Use your program. We'll pretend for this example that your program 2 | creates a complex directory structure. 3 | $ install -d a/b/c/d/e/f 4 | $ install -d a/b2/c2/d2/e2/f2 5 | $ install -d a/b2/c3/d3/e3/f3 6 | $ install -d a/b2/c3/d4/e4/f4 7 | $ install -d a/b2/c3/d4/e5/f5 8 | $ install -d a/b2/c3/d4/e5/f6 9 | $ touch a/b/x 10 | $ touch a/b/c/y 11 | $ touch a/b/c/d/z 12 | 13 | Use diskuvbox to print the directory tree. It should be reproducible 14 | on any platform that Diskuv Box supports! 15 | $ ./diskuvbox.exe tree a --max-depth 10 --encoding UTF-8 16 | a 17 | ├── b/ 18 | │ ├── c/ 19 | │ │ ├── d/ 20 | │ │ │ ├── e/ 21 | │ │ │ │ └── f/ 22 | │ │ │ └── z 23 | │ │ └── y 24 | │ └── x 25 | └── b2/ 26 | ├── c2/ 27 | │ └── d2/ 28 | │ └── e2/ 29 | │ └── f2/ 30 | └── c3/ 31 | ├── d3/ 32 | │ └── e3/ 33 | │ └── f3/ 34 | └── d4/ 35 | ├── e4/ 36 | │ └── f4/ 37 | └── e5/ 38 | ├── f5/ 39 | └── f6/ 40 | -------------------------------------------------------------------------------- /src/bin/tests/tree.t: -------------------------------------------------------------------------------- 1 | Create a complex directory structure 2 | $ install -d a/b/c/d/e/f 3 | $ install -d a/b2/c2/d2/e2/f2 4 | $ install -d a/b2/c3/d3/e3/f3 5 | $ install -d a/b2/c3/d4/e4/f4 6 | $ install -d a/b2/c3/d4/e5/f5 7 | $ install -d a/b2/c3/d4/e5/f6 8 | $ touch a/b/x 9 | $ touch a/b/c/y 10 | $ touch a/b/c/d/z 11 | 12 | Use diskuvbox to print the directory tree with depth <= 0 13 | $ ./diskuvbox.exe tree a --max-depth 0 14 | a 15 | 16 | Use diskuvbox to print the directory tree with depth <= 2 17 | $ ./diskuvbox.exe tree a --max-depth 2 18 | a 19 | |-- b/ 20 | | |-- c/ 21 | | `-- x 22 | `-- b2/ 23 | |-- c2/ 24 | `-- c3/ 25 | 26 | Use diskuvbox to print the directory tree, all of it 27 | $ ./diskuvbox.exe tree a --max-depth 10 28 | a 29 | |-- b/ 30 | | |-- c/ 31 | | | |-- d/ 32 | | | | |-- e/ 33 | | | | | `-- f/ 34 | | | | `-- z 35 | | | `-- y 36 | | `-- x 37 | `-- b2/ 38 | |-- c2/ 39 | | `-- d2/ 40 | | `-- e2/ 41 | | `-- f2/ 42 | `-- c3/ 43 | |-- d3/ 44 | | `-- e3/ 45 | | `-- f3/ 46 | `-- d4/ 47 | |-- e4/ 48 | | `-- f4/ 49 | `-- e5/ 50 | |-- f5/ 51 | `-- f6/ 52 | 53 | Use diskuvbox to print the directory tree, all of it in UTF-8 54 | $ ./diskuvbox.exe tree a --max-depth 10 --encoding UTF-8 55 | a 56 | ├── b/ 57 | │ ├── c/ 58 | │ │ ├── d/ 59 | │ │ │ ├── e/ 60 | │ │ │ │ └── f/ 61 | │ │ │ └── z 62 | │ │ └── y 63 | │ └── x 64 | └── b2/ 65 | ├── c2/ 66 | │ └── d2/ 67 | │ └── e2/ 68 | │ └── f2/ 69 | └── c3/ 70 | ├── d3/ 71 | │ └── e3/ 72 | │ └── f3/ 73 | └── d4/ 74 | ├── e4/ 75 | │ └── f4/ 76 | └── e5/ 77 | ├── f5/ 78 | └── f6/ 79 | 80 | Use diskuvbox to print a subtree of the directory tree 81 | $ ./diskuvbox.exe tree a/b --max-depth 10 82 | a/b 83 | |-- c/ 84 | | |-- d/ 85 | | | |-- e/ 86 | | | | `-- f/ 87 | | | `-- z 88 | | `-- y 89 | `-- x 90 | -------------------------------------------------------------------------------- /src/lib/diskuvbox.ml: -------------------------------------------------------------------------------- 1 | (******************************************************************************) 2 | (* Copyright 2022 Diskuv, Inc. *) 3 | (* *) 4 | (* Licensed under the Apache License, Version 2.0 (the "License"); *) 5 | (* you may not use this file except in compliance with the License. *) 6 | (* You may obtain a copy of the License at *) 7 | (* *) 8 | (* http://www.apache.org/licenses/LICENSE-2.0 *) 9 | (* *) 10 | (* Unless required by applicable law or agreed to in writing, software *) 11 | (* distributed under the License is distributed on an "AS IS" BASIS, *) 12 | (* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *) 13 | (* See the License for the specific language governing permissions and *) 14 | (* limitations under the License. *) 15 | (******************************************************************************) 16 | 17 | open Bos 18 | 19 | type box_error = string -> string 20 | type walk_path = Root | File of Fpath.t | Directory of Fpath.t 21 | type path_attribute = First_in_directory | Last_in_directory [@@deriving ord] 22 | 23 | module Path_attributes = Set.Make (struct 24 | type t = path_attribute 25 | 26 | let compare = compare_path_attribute 27 | end) 28 | 29 | (* Error Handling *) 30 | 31 | let rresult_error_to_string ~err msg = err (Fmt.str "%a" Rresult.R.pp_msg msg) 32 | 33 | let map_rresult_error_to_string ~err = function 34 | | Ok v -> Result.Ok v 35 | | Error msg -> Result.Error (rresult_error_to_string ~err msg) 36 | 37 | let map_string_to_rresult_error = function 38 | | Ok v -> Result.Ok v 39 | | Error s -> Rresult.R.error_msg s 40 | 41 | module type ERROR_HANDLER = sig 42 | val box_error : box_error 43 | end 44 | 45 | module Monad_syntax_rresult (Error_handler : ERROR_HANDLER) = struct 46 | let ( let* ) r (f : 'a -> ('c, 'b) result) = 47 | Rresult.R.bind r (fun a -> 48 | match f a with 49 | | Ok v -> Ok v 50 | | Error msg -> 51 | Rresult.R.error_msg 52 | (Error_handler.box_error 53 | (rresult_error_to_string ~err:Fun.id msg))) 54 | 55 | let ( let+ ) x f = Rresult.R.map f x 56 | end 57 | 58 | let dir_dot = Fpath.v "." 59 | 60 | (** {1 Windows 260 character limit friendly functions} 61 | 62 | Any failures with these functions will tell you to look at the 260 63 | character limit as an explanation. *) 64 | 65 | let windows_max_path = 260 66 | 67 | (** [bos_tmp_name_max] is the maximum length of the basename of 68 | a temporary file created by the Opam/findlib package ["bos"]. *) 69 | let bos_tmp_name_max = String.length "bos-837f7c.tmp" 70 | 71 | let dirsep_length = String.length Fpath.dir_sep 72 | 73 | (** [has_windows_path_problem file] gives true if either the length of [file] 74 | exceeds the Windows maximum {!windows_max_path} or if a temporary file 75 | created by the Opam/findlib package ["bos"] in the directory of [file] 76 | would exceed the Windows maximum {!windows_max_path} *) 77 | let has_windows_path_problem file = 78 | Sys.win32 79 | && (String.length (Fpath.to_string file) >= windows_max_path 80 | || String.length (Fpath.to_string (Fpath.parent file)) 81 | + dirsep_length + bos_tmp_name_max 82 | >= windows_max_path) 83 | 84 | let friendly_write_op f file = 85 | match f () with 86 | | Ok v -> Ok v 87 | | Error m when has_windows_path_problem file -> 88 | Rresult.R.( 89 | error_msg 90 | (Fmt.str 91 | "We recommend that you rename your directories to be smaller \ 92 | because there was a failure writing to the pathname %a. It is \ 93 | likely caused by that pathname (or a temporary filename like \ 94 | bos-837f7c.tmp in the same directory) exceeding the default \ 95 | Windows %d character pathname limit. It may also be what the \ 96 | system reported: %a." 97 | Fpath.pp file windows_max_path pp_msg m)) 98 | | Error msg -> Error msg 99 | 100 | (** Small strings only; maximum string is 16MiB for 32-bit OCaml. 101 | Confer: https://ocamlverse.github.io/content/runtime.html *) 102 | let friendly_write_small_string ?mode file content = 103 | friendly_write_op (fun () -> OS.File.write ?mode file content) file 104 | 105 | let friendly_copyfile ?mode ?(bufsize = 1_048_576) ~err ~src ~dst () = 106 | let open Monad_syntax_rresult (struct 107 | let box_error = err 108 | end) in 109 | let write_file_contents ~output = 110 | let rec helper input = 111 | match input () with 112 | | Some (b, pos, len) -> 113 | output (Some (b, pos, len)); 114 | helper input 115 | | None -> () 116 | in 117 | let buffer = Bytes.create bufsize in 118 | OS.File.with_input ~bytes:buffer src (fun input () -> helper input) () 119 | in 120 | (* Copy file using buffered copy *) 121 | let* nested_result = 122 | friendly_write_op 123 | (fun () -> 124 | let* () = 125 | (* For Windows, can't write without turning off read-only flag. 126 | In fact, you can still get Permission Denied even after turning 127 | off read-only flag, perhaps because Windows has a richer 128 | permissions model than POSIX. So we remove the file 129 | after turning off read-only *) 130 | if Sys.win32 then 131 | let* exists = OS.File.exists dst in 132 | if exists then 133 | let* () = OS.Path.Mode.set dst 0o644 in 134 | OS.File.delete dst 135 | else Ok () 136 | else Ok () 137 | in 138 | OS.File.with_output ?mode dst 139 | (fun output () -> write_file_contents ~output) 140 | ()) 141 | dst 142 | in 143 | nested_result 144 | 145 | (* Public Functions *) 146 | 147 | let current_directory ?(err = Fun.id) () = 148 | map_rresult_error_to_string ~err (OS.Dir.current ()) 149 | 150 | let absolute_path ?(err = Fun.id) fp = 151 | if Fpath.is_abs fp then Result.Ok (Fpath.normalize fp) 152 | else 153 | match current_directory ~err () with 154 | | Ok pwd -> Result.Ok Fpath.(normalize (pwd // fp)) 155 | | Error e -> Error e 156 | 157 | let walk_down ?(err = Fun.id) ?(max_depth = 0) ~from_path ~f () = 158 | let open Monad_syntax_rresult (struct 159 | let box_error = err 160 | end) in 161 | let rec walk walk_path path_on_fs path_attributes depth = 162 | (* pre-order traversal: visit the path first *) 163 | let* () = 164 | map_string_to_rresult_error (f ~depth ~path_attributes walk_path) 165 | in 166 | let* path_is_dir, child_pathize = 167 | match walk_path with 168 | | Root -> 169 | let* dir_exists = OS.Dir.exists path_on_fs in 170 | Ok (dir_exists, fun child -> child) 171 | | File relpath -> 172 | let raise_err _child = 173 | failwith 174 | (Fmt.str 175 | "Should be impossible to descend below the File %a. Started \ 176 | from %a and got to %a" 177 | Fpath.pp relpath Fpath.pp from_path Fpath.pp path_on_fs) 178 | in 179 | Ok (false, raise_err) 180 | | Directory relpath -> Ok (true, fun child -> Fpath.(relpath // child)) 181 | in 182 | match path_is_dir with 183 | | true -> 184 | if depth < max_depth then 185 | (* pre-order traversal: descend last *) 186 | let rec siblings ~first = function 187 | | [] -> Ok () 188 | | hd :: tl -> 189 | let child_path_attributes = 190 | match (first, tl = []) with 191 | | false, true -> Path_attributes.of_list [ Last_in_directory ] 192 | | true, true -> 193 | Path_attributes.of_list 194 | [ First_in_directory; Last_in_directory ] 195 | | true, _ -> Path_attributes.of_list [ First_in_directory ] 196 | | _ -> Path_attributes.empty 197 | in 198 | let child_path_on_fs = Fpath.(path_on_fs // hd) in 199 | let* child_dir_exists = OS.Dir.exists child_path_on_fs in 200 | let* () = 201 | match child_dir_exists with 202 | | true -> 203 | walk 204 | (Directory (child_pathize hd)) 205 | child_path_on_fs child_path_attributes (depth + 1) 206 | | false -> 207 | walk 208 | (File (child_pathize hd)) 209 | child_path_on_fs child_path_attributes (depth + 1) 210 | in 211 | siblings ~first:false tl 212 | in 213 | let* dir_entries = OS.Dir.contents ~rel:true path_on_fs in 214 | let sorted_dir_entries = List.sort Fpath.compare dir_entries in 215 | let* () = siblings ~first:true sorted_dir_entries in 216 | Ok () 217 | else Ok () 218 | | false -> Ok () 219 | in 220 | map_rresult_error_to_string ~err 221 | (let* from_path = OS.Path.must_exist from_path in 222 | walk Root from_path Path_attributes.empty 0) 223 | 224 | let find_up ?(err = Fun.id) ?(max_ascent = 20) ~from_dir ~basenames () = 225 | let open Monad_syntax_rresult (struct 226 | let box_error = err 227 | end) in 228 | let rec validate = function 229 | | [] -> Ok () 230 | | hd :: tl -> ( 231 | let basename_norm = Fpath.normalize hd in 232 | match List.length (Fpath.segs basename_norm) with 233 | | 1 -> validate tl 234 | | 0 -> 235 | Rresult.R.error_msgf 236 | "No basename can be empty. The find-up search was given the \ 237 | following basenames: %a" 238 | Fmt.(Dump.list Fpath.pp) 239 | basenames 240 | | _ -> 241 | Rresult.R.error_msgf 242 | "Basenames cannot have directory separators. The find-up search \ 243 | was given the invalid basename: %a" 244 | Fpath.pp hd) 245 | in 246 | let rec search path basenames_remaining ascents_remaining = 247 | if ascents_remaining <= 0 || Fpath.is_root path then Ok None 248 | else 249 | match basenames_remaining with 250 | | [] -> 251 | let basedir, _rel = Fpath.split_base path in 252 | search basedir basenames (ascents_remaining - 1) 253 | | hd :: tl -> 254 | let candidate = Fpath.(path // hd) in 255 | let* exists = OS.File.exists candidate in 256 | if exists then Ok (Some candidate) 257 | else search path tl ascents_remaining 258 | in 259 | map_rresult_error_to_string ~err 260 | (let* () = validate basenames in 261 | let* from_dir = OS.Dir.must_exist from_dir in 262 | search (Fpath.normalize from_dir) 263 | (List.map Fpath.normalize basenames) 264 | max_ascent) 265 | 266 | let touch_file ?(err = Fun.id) ~file () = 267 | let open Monad_syntax_rresult (struct 268 | let box_error = err 269 | end) in 270 | map_rresult_error_to_string ~err 271 | (let parent_file = Fpath.parent file in 272 | let* created = OS.Dir.create parent_file in 273 | if created then 274 | Logs.debug (fun l -> 275 | l "[touch_file] Created directory %a" Fpath.pp parent_file); 276 | let* exists = OS.File.exists file in 277 | if exists then 278 | (* Modify access and modification times to the current time (0.0). *) 279 | Ok (Unix.utimes (Fpath.to_string file) 0.0 0.0) 280 | else (* Write empty file *) 281 | friendly_write_small_string ~mode:0o644 file "") 282 | 283 | let rewrite_dst ?basename_rewriter ~dst () = 284 | let ( let* ) = Result.bind in 285 | let dst_dir, dst_basename = Fpath.(split_base (normalize dst)) in 286 | let* dst_basename' = 287 | match basename_rewriter with 288 | | Some rw -> rw (Fpath.to_string dst_basename) |> Fpath.of_string 289 | | None -> Ok dst_basename 290 | in 291 | Ok Fpath.(dst_dir // dst_basename') 292 | 293 | let copy_file ?(err = Fun.id) ?bufsize ?mode ?basename_rewriter ~src ~dst () = 294 | let open Monad_syntax_rresult (struct 295 | let box_error = err 296 | end) in 297 | map_rresult_error_to_string ~err 298 | (let* src = OS.File.must_exist src in 299 | let* mode = 300 | match mode with Some m -> Ok m | None -> OS.Path.Mode.get src 301 | in 302 | let* dst = rewrite_dst ?basename_rewriter ~dst () in 303 | let parent_dst = Fpath.parent dst in 304 | let* created = OS.Dir.create parent_dst in 305 | if created then 306 | Logs.debug (fun l -> 307 | l "[copy_file] Created directory %a" Fpath.pp parent_dst); 308 | friendly_copyfile ?bufsize ~mode ~err ~src ~dst ()) 309 | 310 | let copy_dir ?(err = Fun.id) ?bufsize ?basename_rewriter ~src ~dst () = 311 | let open Monad_syntax_rresult (struct 312 | let box_error = err 313 | end) in 314 | let do_copy_dir ~src ~dst = 315 | let raise_fold_error fpath result = 316 | Rresult.R.error_msgf 317 | "@[[copy_dir] A copy directory operation errored out while visiting \ 318 | %a.@]@,\ 319 | @[ @[%a@]@]" Fpath.pp fpath 320 | (Rresult.R.pp 321 | ~ok:(Fmt.any "") 322 | ~error:Rresult.R.pp_msg) 323 | result 324 | in 325 | let cp rel = function 326 | | Error _ as e -> 327 | (* no more copying if we had an error *) 328 | e 329 | | Ok () -> ( 330 | let* rel = 331 | match (Fpath.equal src rel, Fpath.relativize ~root:src rel) with 332 | | true, _ -> Ok dir_dot 333 | | false, Some r -> Ok r 334 | | false, None -> 335 | Rresult.R.error_msg 336 | (Fmt.str 337 | "During copy found a path %a that was not a subpath of \ 338 | the source directory %a" 339 | Fpath.pp rel Fpath.pp src) 340 | in 341 | let src = Fpath.(normalize (src // rel)) 342 | and dst = Fpath.(normalize (dst // rel)) in 343 | let* dst = rewrite_dst ?basename_rewriter ~dst () in 344 | let* isdir = OS.Dir.exists src in 345 | match isdir with 346 | | true -> 347 | let+ created = OS.Dir.create dst in 348 | if created then 349 | Logs.debug (fun l -> 350 | l "[copy_dir] Created directory %a" Fpath.pp dst); 351 | () 352 | | false -> 353 | let* mode = OS.Path.Mode.get src in 354 | let parent_dst = Fpath.parent dst in 355 | let* created = OS.Dir.create parent_dst in 356 | if created then 357 | Logs.debug (fun l -> 358 | l "[copy_dir] Created directory %a" Fpath.pp parent_dst); 359 | let* () = 360 | if Sys.win32 then ( 361 | (* Avoid the error: 362 | rename Z:\\source\\dkml-install-api\\_opam\\.opam-switch\\build\\dkml-installer-network-ocaml.0.4.0\\_build\\installer-work\\archive\\generic\\staging\\staging-unixutils\\generic\\bos-7a2f24.tmp to Z:\\source\\dkml-install-api\\_opam\\.opam-switch\\build\\dkml-installer-network-ocaml.0.4.0\\_build\\installer-work\\archive\\generic\\staging\\staging-unixutils\\generic\\unix_install.bc.exe: Permission denied 363 | Windows does not allow renames if the target file exists. 364 | 365 | But if we simply delete it we get the true error: 366 | delete file Z:\\source\\dkml-install-api\\_opam\\.opam-switch\\build\\dkml-installer-network-ocaml.0.4.0\\_build\\installer-work\\archive\\generic\\staging\\staging-unixutils\\generic\\unix_install.bc.exe: Permission denied 367 | which has permissions: 368 | -r-xr-xr-x 369 | 370 | So bos.0.2.1 is probably trying to delete but not checking 371 | for success, or not deleting at all. Either way it needs 372 | a chmod. Need to upstream a fix with bos.0.2.1 or perhaps 373 | Stdlib! 374 | *) 375 | let* exists = OS.File.exists dst in 376 | if exists then Unix.chmod (Fpath.to_string dst) 0o644; 377 | Ok ()) 378 | else Ok () 379 | in 380 | let+ () = friendly_copyfile ?bufsize ~err ~mode ~src ~dst () in 381 | ()) 382 | in 383 | let* folds = 384 | OS.Path.fold ~err:raise_fold_error ~dotfiles:true cp (Result.Ok ()) 385 | [ src ] 386 | in 387 | match folds with 388 | | Ok () -> Result.Ok () 389 | | Error msg -> 390 | Rresult.R.error_msg 391 | (Fmt.str 392 | "@[[copy_dir] @[Failed to copy the directory@]@[@ from %a@]@[@ to \ 393 | %a@]@]@ @[(%a)@]" 394 | Fpath.pp src Fpath.pp dst Rresult.R.pp_msg msg) 395 | in 396 | map_rresult_error_to_string ~err 397 | (let* src = OS.Dir.must_exist src in 398 | let* abs_src = map_string_to_rresult_error (absolute_path src) in 399 | let* abs_dst = map_string_to_rresult_error (absolute_path dst) in 400 | do_copy_dir ~src:abs_src ~dst:abs_dst) 401 | -------------------------------------------------------------------------------- /src/lib/diskuvbox.mli: -------------------------------------------------------------------------------- 1 | (******************************************************************************) 2 | (* Copyright 2022 Diskuv, Inc. *) 3 | (* *) 4 | (* Licensed under the Apache License, Version 2.0 (the "License"); *) 5 | (* you may not use this file except in compliance with the License. *) 6 | (* You may obtain a copy of the License at *) 7 | (* *) 8 | (* http://www.apache.org/licenses/LICENSE-2.0 *) 9 | (* *) 10 | (* Unless required by applicable law or agreed to in writing, software *) 11 | (* distributed under the License is distributed on an "AS IS" BASIS, *) 12 | (* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *) 13 | (* See the License for the specific language governing permissions and *) 14 | (* limitations under the License. *) 15 | (******************************************************************************) 16 | 17 | type box_error = string -> string 18 | (** The type for managing errors during box operations. 19 | 20 | During a box operation, any error messages are given to this function. 21 | This function can log the error, modify the error message, or raise the 22 | error immediately. *) 23 | 24 | (** The type of path seen during a {!walk_down} operation *) 25 | type walk_path = Root | File of Fpath.t | Directory of Fpath.t 26 | 27 | (** Attributes of the path *) 28 | type path_attribute = First_in_directory | Last_in_directory 29 | 30 | module Path_attributes : Set.S with type elt = path_attribute 31 | 32 | val walk_down : 33 | ?err:box_error -> 34 | ?max_depth:int -> 35 | from_path:Fpath.t -> 36 | f: 37 | (depth:int -> 38 | path_attributes:Path_attributes.t -> 39 | walk_path -> 40 | (unit, string) result) -> 41 | unit -> 42 | (unit, string) result 43 | (** [walk_down ?err ?max_depth ~from_path ~f ()] visits the file [from_path] 44 | or walks down a directory tree [file_path], executing 45 | [f ~depth ~path_attributes path] on every file and directory. 46 | 47 | Symlinks are followed. 48 | 49 | When [from_path] is a file, [f] will be called on [from_path] and that 50 | is the completion of the [walk_down] procedure. 51 | 52 | When [from_path] is a directory, the traversal is pre-order, meaning that 53 | [f] is called on a directory ["A"] before [f] is called on any children of 54 | directory ["A"]. All children in a directory are traversed in lexographical 55 | order. 56 | 57 | The [path] in [f ~depth ~path_attributes path] will be [Root] if the 58 | current file or directory is [from_path]; otherwise the 59 | [path = File relpath] or [path = Directory relpath] has a [relpath] 60 | which is a relative path from [from_path] to the current file or directory. 61 | 62 | The [depth] in [f ~depth ~path_attributes path] will be an integer from 0 63 | to [max_depth], inclusive. 64 | 65 | At most [max_depth] descendants of [from_path] will be walked. When 66 | [max_depth] is [0] no descent into a directory is ever conducted. 67 | The default [max_depth] is 0. 68 | 69 | Any error is passed to [err] if it is specified. The default [err] is 70 | the identity function {!Fun.id}. *) 71 | 72 | val find_up : 73 | ?err:box_error -> 74 | ?max_ascent:int -> 75 | from_dir:Fpath.t -> 76 | basenames:Fpath.t list -> 77 | unit -> 78 | (Fpath.t option, string) result 79 | (** [find_up ?err ?max_ascent ~from_dir ~basenames ()] searches the directory 80 | [from_dir] for any file with a name in the list [basenames]. If not found, 81 | the parent directory of [from_dir] is searched for the file named in 82 | [basenames]. 83 | 84 | At most [max_ascent] ancestors of [from_dir] will be searched 85 | until the file is found. The default [max_ascent] is 20. If the file is 86 | still not found, the function returns [Ok None]. 87 | 88 | An error is reported if [from_dir] is not an existing directory. 89 | 90 | An error is reported if any of the [basenames] names are not true 91 | basenames (there should be no directory components like "." or ".." or "/"). 92 | 93 | Any error is passed to [err] if it is specified. The default [err] is 94 | the identity function {!Fun.id}. *) 95 | 96 | val touch_file : ?err:box_error -> file:Fpath.t -> unit -> (unit, string) result 97 | (** [touch_file ?err ~file ()] creates the file [file] if it does not exist, 98 | creating [file]'s parent directories as necessary. If the [file] 99 | already exists its access and modification times are updated. 100 | 101 | Any error is passed to [err] if it is specified. The default [err] is 102 | the identity function {!Fun.id}. *) 103 | 104 | val copy_file : 105 | ?err:box_error -> 106 | ?bufsize:int -> 107 | ?mode:int -> 108 | ?basename_rewriter:(string -> string) -> 109 | src:Fpath.t -> 110 | dst:Fpath.t -> 111 | unit -> 112 | (unit, string) result 113 | (** [copy_file ?err ?bufsize ?mode ~src ~dst ()] copies the file [src] to the 114 | file [dst], possibly rewriting the destination filenames with [basename_rewriter], 115 | creating [dst]'s parent directories as necessary. 116 | 117 | Copying the file is done through a memory buffer of size [bufsize]. The 118 | default buffer size is large and may vary version to version. We recommend 119 | setting the buffer size explicitly. 120 | 121 | If [mode] is specified, the chmod [mode] will be applied to [dst]. Otherwise 122 | the chmod mode is copied from [src]. 123 | 124 | The [basename_rewriter] operates on the basename of the source file. For example, 125 | if a source file was ["dir1/file1"] and [let base_rewriter s = "rewritten-" ^ s] 126 | then the destination file will be named ["rewritten-file1"] in the destination 127 | directory tree. Embedded subdirectories like [let base_rewriter s = "new/" ^ s] 128 | are accepted as well. 129 | 130 | Any error is passed to [err] if it is specified. The default [err] is 131 | the identity function {!Fun.id}. *) 132 | 133 | val copy_dir : 134 | ?err:box_error -> 135 | ?bufsize:int -> 136 | ?basename_rewriter:(string -> string) -> 137 | src:Fpath.t -> 138 | dst:Fpath.t -> 139 | unit -> 140 | (unit, string) result 141 | (** [copy_dir ?err ?bufsize ~src ~dst ()] copies the contents of [src] into [dst], 142 | possibly rewriting the filenames with [basename_rewriter], creating 143 | [dst] and any parent directories as necessary. 144 | 145 | Copying the files is done through a memory buffer of size [bufsize]. The 146 | default buffer size is large and may vary version to version. We recommend 147 | setting the buffer size explicitly. 148 | 149 | The [basename_rewriter] operates on the basename of the source file. For example, 150 | if a source file was ["dir1/file1"] and [let base_rewriter s = "rewritten-" ^ s] 151 | then the destination file will be named ["rewritten-file1"] in the destination 152 | directory tree. Embedded subdirectories like [let base_rewriter s = "new/" ^ s] 153 | are accepted as well. 154 | 155 | Any error is passed to [err] if it is specified. The default [err] is 156 | the identity function {!Fun.id}. *) 157 | -------------------------------------------------------------------------------- /src/lib/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (name diskuvbox) 3 | (public_name diskuvbox) 4 | (libraries bos fmt fpath rresult) 5 | (preprocess 6 | (pps ppx_deriving.ord))) 7 | 8 | ; The only reason we use a (include ...) clause is so we can import the entire 9 | ; dune.runlicense.inc as a real example inside README.md via the MDX plugin. 10 | ; 11 | ; If you are reading this, do not just blindly copy the (include) clause! 12 | ; Just copy the contents of `dune.runlicense.inc` into your own dune file. 13 | 14 | (include dune.runlicense.inc) 15 | -------------------------------------------------------------------------------- /src/lib/dune.runlicense.inc: -------------------------------------------------------------------------------- 1 | ; This first rule creates "corrected" source code in the Dune build directory 2 | ; that always has an Apache v2.0 license at the top of each file. 3 | (rule 4 | (targets diskuvbox.corrected.ml diskuvbox.corrected.mli) 5 | (deps 6 | (:license %{project_root}/etc/license-header.txt) 7 | (:conf %{project_root}/etc/headache.conf)) 8 | (action 9 | (progn 10 | ; `headache` adds/replaces headers in source code. It is documented at 11 | ; https://github.com/Frama-C/headache/#readme 12 | ; 13 | ; 1. The `headache` program modifies files in-place, so we make a copy of 14 | ; the original file. 15 | ; 2. On Windows `heachache` can fail with "Permission denied" if we don't 16 | ; set write permissions on the file. 17 | ; `diskuvbox` can accomplish both goals on all its supported platforms. 18 | (run diskuvbox copy-file -m 644 diskuvbox.ml diskuvbox.corrected.ml) 19 | (run diskuvbox copy-file -m 644 diskuvbox.mli diskuvbox.corrected.mli) 20 | ; Add Apache v2.0 license to each file 21 | (run headache -h %{license} -c %{conf} %{targets}) 22 | ; 23 | ; `ocamlformat` is used so that our source code modification is idempotent. 24 | ; (Advanced: Options chosen so that continuous integration tests work with 25 | ; any version of `ocamlformat`.) 26 | (run ocamlformat --inplace --disable-conf-files --enable-outside-detected-project %{targets})))) 27 | 28 | ; These second set of rules let us type: 29 | ; dune build @runlicense --auto-promote 30 | ; 31 | ; Anytime we type that Dune will take the corrected source code from the Dune 32 | ; build directory and use it to modify the original source code. 33 | (rule 34 | (alias runlicense) 35 | (action 36 | (diff diskuvbox.ml diskuvbox.corrected.ml))) 37 | (rule 38 | (alias runlicense) 39 | (action 40 | (diff diskuvbox.mli diskuvbox.corrected.mli))) 41 | -------------------------------------------------------------------------------- /support/.gitignore: -------------------------------------------------------------------------------- 1 | dockcross-manylinux2014-x86.sh 2 | -------------------------------------------------------------------------------- /support/test-32bit-helper.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -euf 3 | 4 | # Install Opam 5 | printf "\n\n[linux-32bit]$ # \e[31mInstalling Opam ...\e[0m\n\n" 6 | echo /usr/local/bin | bash -c "sh <(curl -fsSL https://raw.githubusercontent.com/ocaml/opam/cbd243246aba43d2ad0ec2b461ac8486d5881bc4/shell/install.sh)" 7 | echo 8 | 9 | opamrun() { 10 | printf "\n\n[linux-32bit]$ \e[31mopam %s\e[0m\n\n" "$*" >&2 11 | /usr/local/bin/opam "$@" 12 | } 13 | 14 | # Init opam 15 | opamrun init --disable-sandboxing --no-setup --compiler=ocaml-base-compiler.4.12.1 -y 16 | 17 | # Install diskuvbox 18 | opamrun install . --yes 19 | 20 | # [file_in_mb FILE MB] create a sparse file FILE of size MB kilobytes. 21 | file_in_mb() { 22 | file_in_mb_FILE=$1 23 | shift 24 | file_in_mb_MB=$1 25 | shift 26 | if command -v truncate >/dev/null 2>/dev/null; then 27 | truncate -s "$file_in_mb_MB"M "$file_in_mb_FILE" 28 | else 29 | dd if=/dev/zero of="$file_in_mb_FILE" bs=1048576 count=0 seek="$file_in_mb_MB" 30 | fi 31 | } 32 | 33 | # Regression test #1 34 | # https://github.com/diskuv/diskuvbox/issues/1 35 | file_in_mb _build/test_20m 20 36 | opamrun exec -- env OCAMLRUNPARAM=b diskuvbox copy-file -vv _build/test_20m _build/dest/test_20m 37 | 38 | # Regression test #2 39 | # Same as https://github.com/diskuv/diskuvbox/issues/1, but see if 32-bit can 40 | # create a larger than 32-bit file. 41 | # 42 | # 2022-08-06: Fails! 43 | # 44 | file_in_mb _build/test_5gb 5120 45 | # Continue after failure. 46 | set +e 47 | # Here is the original failure 48 | opamrun exec -- env OCAMLRUNPARAM=b dune exec -- src/bin/main.exe copy-file -vv _build/test_5gb _build/dest/test_5gb 49 | # Here is a smallest reproducible test case 50 | echo 51 | ls -lh _build/test_20m _build/test_5gb 52 | opamrun exec -- ocaml < support/test_32bit_bos.ml 53 | 54 | # Troubleshooting 55 | # shellcheck disable=SC2016 56 | printf '\n\nEntering a bash shell for you to see what is wrong. Use [opam exec --] or [eval $(opam env)] to run commands. Type [exit] when done\n\n' 57 | exec bash -l 58 | -------------------------------------------------------------------------------- /support/test-32bit.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Prerequisites: 4 | # * Docker 5 | # 6 | # Tested on: 7 | # * Windows 64-bit with Diskuv OCaml and `with-dkml support/test-32bit.sh` 8 | # 9 | # Probably works: 10 | # * Linux 32-bit or 64-bit. Use `sh support/test-32bit.sh` 11 | # 12 | # Won't work: 13 | # * macOS ARM64; it won't let you run Docker with linux 32-bit, probably 14 | # because 32-bit circuitry was removed from Apple Silicon chips 15 | 16 | set -euf 17 | 18 | # Go to project directory 19 | HERE=$(dirname "$0") 20 | HERE=$(cd "$HERE" && pwd) 21 | cd "$HERE/.." 22 | 23 | # Get dockcross launch script 24 | DOCKCROSS_SH=support/dockcross-manylinux2014-x86.sh 25 | if [ ! -e ${DOCKCROSS_SH} ]; then 26 | if command -v dos2unix; then 27 | docker run --platform linux/386 -it dockcross/manylinux2014-x86:latest | dos2unix > ${DOCKCROSS_SH}.tmp 28 | else 29 | docker run --platform linux/386 -it dockcross/manylinux2014-x86:latest > ${DOCKCROSS_SH}.tmp 30 | fi 31 | chmod +x ${DOCKCROSS_SH}.tmp 32 | mv ${DOCKCROSS_SH}.tmp ${DOCKCROSS_SH} 33 | fi 34 | 35 | # Run code in test-32bit-helper.sh. The diskuvbox project directory is 36 | # mounted at /work 37 | exec ${DOCKCROSS_SH} --args '-it --platform linux/386' support/test-32bit-helper.sh 38 | -------------------------------------------------------------------------------- /support/test_32bit_bos.ml: -------------------------------------------------------------------------------- 1 | #use "topfind" 2 | 3 | #require "bos" 4 | 5 | #require "fmt" 6 | 7 | #require "fpath" 8 | 9 | open Bos 10 | 11 | let print_result = 12 | Fmt.pr "Result: %a@\n" 13 | Rresult.R.( 14 | pp 15 | ~ok: 16 | Fmt.( 17 | const string "Ok ( Fpath.v {|" ++ Fpath.pp ++ const string "|} )") 18 | ~error: 19 | Fmt.(const string "Error ( `Msg {|" ++ pp_msg ++ const string "|} )")) 20 | ;; 21 | 22 | print_endline "\n\n========= 20 MB ========\n\n";; 23 | print_result @@ OS.File.must_exist (Fpath.v "_build/test_20m");; 24 | print_endline "\n\n========= 5 GB ========\n\n";; 25 | print_result @@ OS.File.must_exist (Fpath.v "_build/test_5gb") 26 | --------------------------------------------------------------------------------