diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index b34d3a8..a75cf03 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -3,8 +3,8 @@ name: build-and-test on: pull_request: push: - branches-ignore: - - gh-pages + branches: + - main jobs: build: @@ -13,8 +13,11 @@ jobs: matrix: os: - ubuntu-latest + - macos-latest ocaml-compiler: - - 5.0.0 + - 4.12.0 + - 4.x + - 5.x runs-on: ${{ matrix.os }} @@ -28,7 +31,10 @@ jobs: ocaml-compiler: ${{ matrix.ocaml-compiler }} - name: Install dependencies - run: opam pin . --with-doc --with-test --yes + run: opam install . --deps-only --with-test - - name: Build and test - run: ./script/ci + - name: Build + run: opam exec -- dune build + + - name: Test + run: opam exec -- dune runtest diff --git a/.gitignore b/.gitignore index 76301f0..e35d885 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1 @@ _build -_opam diff --git a/.ocamlformat b/.ocamlformat index 08df15c..6b48b9a 100644 --- a/.ocamlformat +++ b/.ocamlformat @@ -1,12 +1,4 @@ -version=0.24.1 +profile = default +version = 0.25.1 -module-item-spacing=compact - -space-around-arrays=false -space-around-lists=false -space-around-records=false -space-around-variants=false - -cases-exp-indent=2 - -exp-grouping=preserve +exp-grouping = preserve diff --git a/CHANGES.md b/CHANGES.md new file mode 100644 index 0000000..7ae6de2 --- /dev/null +++ b/CHANGES.md @@ -0,0 +1,7 @@ +# Release notes + +All notable changes to this project will be documented in this file. + +## 1.0.0 + +- Initial release (@polytypic) diff --git a/README.md b/README.md index b8bbabb..36a8aac 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,22 @@ -# Low-level multicore utilities for OCaml +[API reference](https://ocaml-multicore.github.io/multicore-magic/doc/multicore-magic/Multicore_magic/index.html) + +# **multicore-magic** — Low-level multicore utilities for OCaml This is a library of magic multicore utilities intended for experts for extracting the best possible performance from multicore OCaml. Hopefully future releases of multicore OCaml will make this library obsolete! -See -[the reference manual](https://ocaml-multicore.github.io/multicore-magic/multicore-magic/Multicore_magic/index.html) -or the [Multicore_magic.mli](src/main/Multicore_magic.mli) signature for the -API. +## Development + +### Formatting + +This project uses [ocamlformat](https://github.com/ocaml-ppx/ocamlformat) (for +OCaml) and [prettier](https://prettier.io/) (for Markdown). + +### To make a new release + +1. Update [CHANGES.md](CHANGES.md). +2. Run `dune-release tag VERSION` to create a tag for the new `VERSION`. +3. Run `dune-release` to publish the new `VERSION`. +4. Run `./update-gh-pages-for-tag VERSION` to update the online documentation. diff --git a/docs/.gitignore b/docs/.gitignore deleted file mode 100644 index e69de29..0000000 diff --git a/dune b/dune index ba31389..2aac24f 100644 --- a/dune +++ b/dune @@ -1 +1 @@ -(dirs src) +(dirs src test) diff --git a/dune-project b/dune-project index 7b17fb2..d59d8e2 100644 --- a/dune-project +++ b/dune-project @@ -1 +1,14 @@ (lang dune 3.3) +(name multicore-magic) +(generate_opam_files true) +(source (github ocaml-multicore/multicore-magic)) +(authors "Vesa Karvonen ") +(maintainers "Vesa Karvonen ") +(homepage "https://github.com/ocaml-multicore/multicore-magic") +(license ISC) +(package (name multicore-magic) + (synopsis "Low-level multicore utilities for OCaml") + (depends + (ocaml (>= 4.12.0)) + (alcotest (and (>= 1.7.0) :with-test)) + (odoc (and (>= 2.2.0) :with-doc)))) diff --git a/multicore-magic.opam b/multicore-magic.opam index 1d68d18..2386dbe 100644 --- a/multicore-magic.opam +++ b/multicore-magic.opam @@ -1,16 +1,29 @@ +# This file is generated by dune, edit dune-project instead opam-version: "2.0" -version: "1.0.0" synopsis: "Low-level multicore utilities for OCaml" -maintainer: "Vesa Karvonen " -authors: "Vesa Karvonen" +maintainer: ["Vesa Karvonen "] +authors: ["Vesa Karvonen "] license: "ISC" homepage: "https://github.com/ocaml-multicore/multicore-magic" bug-reports: "https://github.com/ocaml-multicore/multicore-magic/issues" -dev-repo: "git+https://github.com/ocaml-multicore/multicore-magic.git" depends: [ - "dune" {>= "3.3.0"} - "ocaml" {>= "5.0.0"} - "ocamlformat" {= "0.24.1" & with-test} - "odoc" {>= "2.1.1" & with-doc} + "dune" {>= "3.3"} + "ocaml" {>= "4.12.0"} + "alcotest" {>= "1.7.0" & with-test} + "odoc" {>= "2.2.0" & with-doc} +] +build: [ + ["dune" "subst"] {dev} + [ + "dune" + "build" + "-p" + name + "-j" + jobs + "@install" + "@runtest" {with-test} + "@doc" {with-doc} + ] ] -build: ["dune" "build" "-p" name "-j" jobs] +dev-repo: "git+https://github.com/ocaml-multicore/multicore-magic.git" diff --git a/script/ci b/script/ci deleted file mode 100755 index 0250b55..0000000 --- a/script/ci +++ /dev/null @@ -1,49 +0,0 @@ -#!/bin/bash - -. "${BASH_SOURCE%/*}/common" - -main() { - if [ "$CI" = true ]; then - PROFILE='' build-and-test - clean - fi - PROFILE=release build-and-test - build-docs - check-git-clean -} - -build-and-test() { - OPTS=(--root=.) - - if [ -n "$PROFILE" ]; then - OPTS+=(--profile "$PROFILE") - fi - - folded "Building" \ - opam exec -- dune build "${OPTS[@]}" - - folded "Testing" \ - opam exec -- dune test "${OPTS[@]}" -} - -clean() { - folded "Cleaning" \ - opam exec -- dune clean -} - -build-docs() { - folded "Build docs" \ - opam exec -- dune build @doc --root=. -} - -check-git-clean() { - if [ "$CI" = true ]; then - if [[ $(git status --porcelain) ]] ; then - git status - git diff - exit 1 - fi - fi -} - -main diff --git a/script/common b/script/common deleted file mode 100755 index 912caa4..0000000 --- a/script/common +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash - -set -eo pipefail - -export PROJECT=multicore-magic -export DOCS=_build/default/_doc/_html/ -export GIT=git@github.com:ocaml-multicore/$PROJECT.git - -folded() { - echo - echo "JOB: $1" - shift - local TIMEFORMAT="CPU: %Us, Real: %Es" - time "$@" -} diff --git a/script/publish-gh-pages b/script/publish-gh-pages deleted file mode 100755 index 37fce23..0000000 --- a/script/publish-gh-pages +++ /dev/null @@ -1,41 +0,0 @@ -#!/bin/bash - -. "${BASH_SOURCE%/*}/common" - -main() { - clone-repo - build-and-test - publish-docs - cleanup -} - -clone-repo() { - mkdir tmp - cd tmp - - git clone $GIT - cd $PROJECT -} - -build-and-test() { - CI=true script/ci -} - -publish-docs() { - git checkout -b gh-pages - - rm docs/.gitignore - - cp -r _build/default/_doc/_html/* docs/ - - git add docs - git commit -m 'Built GitHub pages' - git push -f -u origin gh-pages -} - -cleanup() { - cd ../.. - rm -rf tmp -} - -main diff --git a/script/watch b/script/watch deleted file mode 100755 index 1a14bf8..0000000 --- a/script/watch +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash - -. "${BASH_SOURCE%/*}/common" - -tmux start-server -tmux new-session -d -s $PROJECT - -tmux splitw -v -p 90 -tmux select-pane -t 0 -tmux splitw -h - -tmux select-pane -t 0 -tmux send-keys "npx livereload $DOCS --wait 250" C-m - -tmux select-pane -t 1 -tmux send-keys "npx serve $DOCS" C-m - -tmux select-pane -t 2 -tmux send-keys "script/watch-test" C-m - -tmux attach-session -t $PROJECT diff --git a/script/watch-test b/script/watch-test deleted file mode 100755 index 99355f5..0000000 --- a/script/watch-test +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash - -. "${BASH_SOURCE%/*}/common" - -COMMAND="(\ - echo $'<<< <<< <<<' && \ - ./script/ci && \ - echo $'\n>>> >>> >>>' \ -)" - -if command -v watchexec > /dev/null; then - watchexec --debounce 250 -- "$COMMAND" -else - >&2 echo "WARNING: Could not find suitable file watcher." - exit 1 -fi diff --git a/src/main/Multicore_magic.ml b/src/Multicore_magic.ml similarity index 100% rename from src/main/Multicore_magic.ml rename to src/Multicore_magic.ml diff --git a/src/main/Multicore_magic.mli b/src/Multicore_magic.mli similarity index 100% rename from src/main/Multicore_magic.mli rename to src/Multicore_magic.mli diff --git a/src/main/dune b/src/dune similarity index 100% rename from src/main/dune rename to src/dune diff --git a/src/test/Multicore_magic_test.ml b/src/test/Multicore_magic_test.ml deleted file mode 100644 index b577347..0000000 --- a/src/test/Multicore_magic_test.ml +++ /dev/null @@ -1,53 +0,0 @@ -let () = assert (!(Multicore_magic.copy_as_padded (ref 101)) = 101) - -let () = - assert (Atomic.get (Multicore_magic.copy_as_padded (Atomic.make 42)) = 42) - -let () = - let open struct - type record = {foo : int; bar : int Atomic.t} - end in - let foo = 42 and bar = Atomic.make 101 in - let x = Multicore_magic.copy_as_padded {foo; bar} in - assert (x.foo = foo && x.bar == bar) - -let () = - let open struct - type variant = Foo of int * int Atomic.t - end in - let foo = 19 and bar = Atomic.make 76 in - let (Foo (foo', bar')) = Multicore_magic.copy_as_padded (Foo (foo, bar)) in - assert (foo = foo' && bar == bar') - -let () = - let xs = Multicore_magic.make_padded_array 5 101 in - assert (5 <= Array.length xs); - for i = 0 to 4 do - assert (xs.(i) = 101) - done - -let () = - assert ( - Multicore_magic.length_of_padded_array - (Multicore_magic.make_padded_array 42 0) - = 42) - -let () = - assert ( - Multicore_magic.length_of_padded_array_minus_1 - (Multicore_magic.make_padded_array 101 0) - = 100) - -let () = assert (Multicore_magic.fenceless_get (Atomic.make 42) = 42) - -let () = - let x = Atomic.make 42 in - Multicore_magic.fenceless_set x 101; - assert (Multicore_magic.fenceless_get x = 101); - assert (Atomic.get x = 101) - -let () = - assert ( - let atomic = Atomic.make 76 in - Multicore_magic.fence atomic; - Atomic.get atomic = 76) diff --git a/src/test/dune b/src/test/dune deleted file mode 100644 index 5b8e91d..0000000 --- a/src/test/dune +++ /dev/null @@ -1,3 +0,0 @@ -(tests - (names Multicore_magic_test) - (libraries Multicore_magic)) diff --git a/test/Multicore_magic_test.ml b/test/Multicore_magic_test.ml new file mode 100644 index 0000000..f8bf9ef --- /dev/null +++ b/test/Multicore_magic_test.ml @@ -0,0 +1,71 @@ +let can_pad_ref () = assert (!(Multicore_magic.copy_as_padded (ref 101)) = 101) + +let can_pad_atomic () = + assert (Atomic.get (Multicore_magic.copy_as_padded (Atomic.make 42)) = 42) + +let can_pad_records () = + let open struct + type record = { foo : int; bar : int Atomic.t } + end in + let foo = 42 and bar = Atomic.make 101 in + let x = Multicore_magic.copy_as_padded { foo; bar } in + assert (x.foo = foo && x.bar == bar) + +let can_pad_variants () = + let open struct + type variant = Foo of int * int Atomic.t + end in + let foo = 19 and bar = Atomic.make 76 in + let (Foo (foo', bar')) = Multicore_magic.copy_as_padded (Foo (foo, bar)) in + assert (foo = foo' && bar == bar') + +let can_pad_arrays () = + let xs = Multicore_magic.make_padded_array 5 101 in + assert (5 <= Array.length xs); + for i = 0 to 4 do + assert (xs.(i) = 101) + done + +let padded_array_length () = + assert ( + Multicore_magic.length_of_padded_array + (Multicore_magic.make_padded_array 42 0) + = 42) + +let padded_array_length_minus_1 () = + assert ( + Multicore_magic.length_of_padded_array_minus_1 + (Multicore_magic.make_padded_array 101 0) + = 100) + +let fenceless_get () = + assert (Multicore_magic.fenceless_get (Atomic.make 42) = 42) + +let fenceless_set () = + let x = Atomic.make 42 in + Multicore_magic.fenceless_set x 101; + assert (Multicore_magic.fenceless_get x = 101); + assert (Atomic.get x = 101) + +let fence () = + assert ( + let atomic = Atomic.make 76 in + Multicore_magic.fence atomic; + Atomic.get atomic = 76) + +let () = + Alcotest.run "multicore-magic" + [ + ("can pad ref", [ Alcotest.test_case "" `Quick can_pad_ref ]); + ("can pad atomic", [ Alcotest.test_case "" `Quick can_pad_atomic ]); + ("can pad records", [ Alcotest.test_case "" `Quick can_pad_records ]); + ("can pad variants", [ Alcotest.test_case "" `Quick can_pad_variants ]); + ("can pad arrays", [ Alcotest.test_case "" `Quick can_pad_arrays ]); + ( "padded array length", + [ Alcotest.test_case "" `Quick padded_array_length ] ); + ( "padded array length - 1", + [ Alcotest.test_case "" `Quick padded_array_length_minus_1 ] ); + ("fenceless_get", [ Alcotest.test_case "" `Quick fenceless_get ]); + ("fenceless_set", [ Alcotest.test_case "" `Quick fenceless_set ]); + ("fence", [ Alcotest.test_case "" `Quick fence ]); + ] diff --git a/test/dune b/test/dune new file mode 100644 index 0000000..ded08a3 --- /dev/null +++ b/test/dune @@ -0,0 +1,3 @@ +(tests + (names Multicore_magic_test) + (libraries Multicore_magic alcotest)) diff --git a/update-gh-pages-for-tag b/update-gh-pages-for-tag new file mode 100755 index 0000000..d8a2545 --- /dev/null +++ b/update-gh-pages-for-tag @@ -0,0 +1,75 @@ +#!/bin/bash + +set -xeuo pipefail + +TMP=tmp +NAME=multicore-magic +MAIN=doc +GIT="git@github.com:ocaml-multicore/$NAME.git" +DOC="_build/default/_doc/_html" +GH_PAGES=gh-pages + +TAG="$1" + +if ! [ -e $NAME.opam ] || [ $# -ne 1 ] || \ + { [ "$TAG" != main ] && ! [ "$(git tag -l "$TAG")" ]; }; then + CMD="${0##*/}" + cat << EOF +Usage: $CMD tag-name-or-main + +This script +- clones the repository into a temporary directory ($TMP/$NAME), +- builds the documentation for the specified tag or main, +- updates $GH_PAGES branch with the documentation in directory for the tag, +- prompts whether to also update the main documentation in $MAIN directory, and +- prompts whether to push changes to $GH_PAGES. + +EOF + exit 1 +fi + +mkdir $TMP +cd $TMP + +git clone $GIT +cd $NAME + +git checkout "$TAG" +dune build @doc --root=. + +git checkout $GH_PAGES +if [ "$TAG" != main ]; then + echo "Updating the $TAG doc." + if [ -e "$TAG" ]; then + git rm -rf "$TAG" + fi + cp -r $DOC "$TAG" + git add "$TAG" +fi + +read -p "Update the main doc? (y/N) " -n 1 -r +echo +if [[ $REPLY =~ ^[Yy]$ ]]; then + if [ -e $MAIN ]; then + git rm -rf $MAIN + fi + cp -r $DOC $MAIN + git add $MAIN +else + echo "Skipped main doc update." +fi + +git commit -m "Update $NAME doc for $TAG" + +read -p "Push changes to $GH_PAGES? (y/N) " -n 1 -r +echo +if ! [[ $REPLY =~ ^[Yy]$ ]]; then + echo "Leaving $TMP for you to examine." + exit 1 +fi + +git push + +cd .. +cd .. +rm -rf $TMP