diff --git a/.ci-scripts/release/x86-64-apple-darwin-nightly.bash b/.ci-scripts/release/x86-64-apple-darwin-nightly.bash new file mode 100644 index 0000000..d8b01ec --- /dev/null +++ b/.ci-scripts/release/x86-64-apple-darwin-nightly.bash @@ -0,0 +1,101 @@ +#!/bin/bash + +# x86-64-unknown-linux release: +# +# - Builds release package +# - Uploads to Cloudsmith +# +# Tools required in the environment that runs this: +# +# - bash +# - cloudsmith-cli +# - GNU gzip +# - GNU make +# - ponyc +# - GNU tar + +set -o errexit + +# Pull in shared configuration specific to this repo +base=$(dirname "$0") +source "${base}/config.bash" + +# Verify ENV is set up correctly +# We validate all that need to be set in case, in an absolute emergency, +# we need to run this by hand. Otherwise the GitHub actions environment should +# provide all of these if properly configured +if [[ -z "${CLOUDSMITH_API_KEY}" ]]; then + echo -e "\e[31mCloudsmith API key needs to be set in CLOUDSMITH_API_KEY." + echo -e "Exiting.\e[0m" + exit 1 +fi + +if [[ -z "${GITHUB_REPOSITORY}" ]]; then + echo -e "\e[31mName of this repository needs to be set in GITHUB_REPOSITORY." + echo -e "\e[31mShould be in the form OWNER/REPO, for example:" + echo -e "\e[31m ponylang/ponyup" + echo -e "\e[31mExiting.\e[0m" + exit 1 +fi + +if [[ -z "${APPLICATION_NAME}" ]]; then + echo -e "\e[31mAPPLICATION_NAME needs to be set." + echo -e "\e[31mExiting.\e[0m" + exit 1 +fi + +if [[ -z "${APPLICATION_SUMMARY}" ]]; then + echo -e "\e[31mAPPLICATION_SUMMARY needs to be set." + echo -e "\e[31mIt's a short description of the application that will appear in Cloudsmith." + echo -e "\e[31mExiting.\e[0m" + exit 1 +fi + +# no unset variables allowed from here on out +# allow above so we can display nice error messages for expected unset variables +set -o nounset + +TODAY=$(date +%Y%m%d) + +# Compiler target parameters +ARCH=x86-64 + +# Triple construction +VENDOR=apple +OS=darwin +TRIPLE=${ARCH}-${VENDOR}-${OS} + +# Build parameters +BUILD_PREFIX=$(mktemp -d) +APPLICATION_VERSION="nightly-${TODAY}" +BUILD_DIR=${BUILD_PREFIX}/${APPLICATION_VERSION} + +# Asset information +PACKAGE_DIR=$(mktemp -d) +PACKAGE=${APPLICATION_NAME}-${TRIPLE} + +# Cloudsmith configuration +CLOUDSMITH_VERSION=${TODAY} +ASSET_OWNER=ponylang +ASSET_REPO=nightlies +ASSET_PATH=${ASSET_OWNER}/${ASSET_REPO} +ASSET_FILE=${PACKAGE_DIR}/${PACKAGE}.tar.gz +ASSET_SUMMARY="${APPLICATION_SUMMARY}" +ASSET_DESCRIPTION="https://github.com/${GITHUB_REPOSITORY}" + +# Build application installation +echo -e "\e[34mBuilding ${APPLICATION_NAME}...\e[0m" +make install prefix="${BUILD_DIR}" arch=${ARCH} \ + version="${APPLICATION_VERSION}" + +# Package it all up +echo -e "\e[34mCreating .tar.gz of ${APPLICATION_NAME}...\e[0m" +pushd "${BUILD_PREFIX}" || exit 1 +tar -cvzf "${ASSET_FILE}" * +popd || exit 1 + +# Ship it off to cloudsmith +echo -e "\e[34mUploading package to cloudsmith...\e[0m" +cloudsmith push raw --version "${CLOUDSMITH_VERSION}" \ + --api-key "${CLOUDSMITH_API_KEY}" --summary "${ASSET_SUMMARY}" \ + --description "${ASSET_DESCRIPTION}" ${ASSET_PATH} "${ASSET_FILE}" diff --git a/.ci-scripts/release/x86-64-apple-darwin-release.bash b/.ci-scripts/release/x86-64-apple-darwin-release.bash new file mode 100644 index 0000000..85649f8 --- /dev/null +++ b/.ci-scripts/release/x86-64-apple-darwin-release.bash @@ -0,0 +1,99 @@ +#!/bin/bash + +# x86-64-unknown-linux release: +# +# - Builds release package +# - Uploads to Cloudsmith +# +# Tools required in the environment that runs this: +# +# - bash +# - cloudsmith-cli +# - GNU gzip +# - GNU make +# - ponyc +# - GNU tar + +set -o errexit + +# Pull in shared configuration specific to this repo +base=$(dirname "$0") +source "${base}/config.bash" + +# Verify ENV is set up correctly +# We validate all that need to be set in case, in an absolute emergency, +# we need to run this by hand. Otherwise the GitHub actions environment should +# provide all of these if properly configured +if [[ -z "${CLOUDSMITH_API_KEY}" ]]; then + echo -e "\e[31mCloudsmith API key needs to be set in CLOUDSMITH_API_KEY." + echo -e "Exiting.\e[0m" + exit 1 +fi + +if [[ -z "${GITHUB_REPOSITORY}" ]]; then + echo -e "\e[31mName of this repository needs to be set in GITHUB_REPOSITORY." + echo -e "\e[31mShould be in the form OWNER/REPO, for example:" + echo -e "\e[31m ponylang/ponyup" + echo -e "\e[31mExiting.\e[0m" + exit 1 +fi + +if [[ -z "${APPLICATION_NAME}" ]]; then + echo -e "\e[31mAPPLICATION_NAME needs to be set." + echo -e "\e[31mExiting.\e[0m" + exit 1 +fi + +if [[ -z "${APPLICATION_SUMMARY}" ]]; then + echo -e "\e[31mAPPLICATION_SUMMARY needs to be set." + echo -e "\e[31mIt's a short description of the application that will appear in Cloudsmith." + echo -e "\e[31mExiting.\e[0m" + exit 1 +fi + +# no unset variables allowed from here on out +# allow above so we can display nice error messages for expected unset variables +set -o nounset + +# Compiler target parameters +ARCH=x86-64 + +# Triple construction +VENDOR=apple +OS=darwin +TRIPLE=${ARCH}-${VENDOR}-${OS} + +# Build parameters +BUILD_PREFIX=$(mktemp -d) +APPLICATION_VERSION=$(cat VERSION) +BUILD_DIR=${BUILD_PREFIX}/${APPLICATION_VERSION} + +# Asset information +PACKAGE_DIR=$(mktemp -d) +PACKAGE=${APPLICATION_NAME}-${TRIPLE} + +# Cloudsmith configuration +CLOUDSMITH_VERSION=$(cat VERSION) +ASSET_OWNER=ponylang +ASSET_REPO=releases +ASSET_PATH=${ASSET_OWNER}/${ASSET_REPO} +ASSET_FILE=${PACKAGE_DIR}/${PACKAGE}.tar.gz +ASSET_SUMMARY="${APPLICATION_SUMMARY}" +ASSET_DESCRIPTION="https://github.com/${GITHUB_REPOSITORY}" + +# Build application installation +echo -e "\e[34mBuilding ${APPLICATION_NAME}...\e[0m" +make install prefix="${BUILD_DIR}" arch=${ARCH} \ + version="${APPLICATION_VERSION}" + +# Package it all up +echo -e "\e[34mCreating .tar.gz of ${APPLICATION_NAME}...\e[0m" +pushd "${BUILD_PREFIX}" || exit 1 +tar -cvzf "${ASSET_FILE}" * +popd || exit 1 + +# Ship it off to cloudsmith +echo -e "\e[34mUploading package to cloudsmith...\e[0m" +cloudsmith push raw --version "${CLOUDSMITH_VERSION}" \ + --api-key "${CLOUDSMITH_API_KEY}" --summary "${ASSET_SUMMARY}" \ + --description "${ASSET_DESCRIPTION}" ${ASSET_PATH} "${ASSET_FILE}" diff --git a/.ci-scripts/test-bootstrap.sh b/.ci-scripts/test-bootstrap.sh index cc35e36..76f114d 100755 --- a/.ci-scripts/test-bootstrap.sh +++ b/.ci-scripts/test-bootstrap.sh @@ -1,8 +1,5 @@ #!/bin/sh -triple="$(cc -dumpmachine)" -libc="${triple##*-}" - rm -rf \ /usr/local/bin/ponyc \ /usr/local/bin/stable \ @@ -13,7 +10,7 @@ rm -rf \ cat ponyup-init.sh | sh -s -- --prefix=/usr/local export PATH=$HOME/.local/share/ponyup/bin:$PATH -ponyup update ponyc nightly --libc=${libc} +ponyup update ponyc nightly "--platform=$(cc -dumpmachine)" ponyup update changelog-tool nightly ponyup update corral nightly ponyup update stable nightly diff --git a/.github/workflows/nightlies.yml b/.github/workflows/nightlies.yml index 237f2f6..daad391 100644 --- a/.github/workflows/nightlies.yml +++ b/.github/workflows/nightlies.yml @@ -17,3 +17,18 @@ jobs: env: CLOUDSMITH_API_KEY: ${{ secrets.CLOUDSMITH_API_KEY }} + x86-64-apple-darwin-nightly: + name: Build and upload x86-64-apple-darwin-nightly to Cloudsmith + runs-on: macos-latest + steps: + - uses: actions/checkout@v1 + - name: brew install ponyc + run: brew install ponyc + - name: brew install dependencies + run: brew install coreutils libressl python + - name: pip install dependencies + run: pip3 install --upgrade cloudsmith-cli + - name: Build and upload + run: bash .ci-scripts/release/x86-64-apple-darwin-nightly.bash + env: + CLOUDSMITH_API_KEY: ${{ secrets.CLOUDSMITH_API_KEY }} diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 7c0c8cc..1650a36 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -31,15 +31,28 @@ jobs: - name: Verify CHANGELOG run: changelog-tool verify - vs-ponyc-release: - name: Verify PR builds most recent ponyc release + linux: + name: Verify PR builds on Linux with most recent ponyc release runs-on: ubuntu-latest container: image: ponylang/shared-docker-ci-x86-64-unknown-linux-builder-with-ssl:release steps: - uses: actions/checkout@v1 - name: Test with the most recent ponyc release - run: make test + run: PONYUP_PLATFORM=musl make test - name: Bootstrap test run: .ci-scripts/test-bootstrap.sh + macos: + name: Verify PR builds on macOS with most recent ponyc release + runs-on: macos-latest + steps: + - uses: actions/checkout@v1 + - name: brew install ponyc + run: brew install ponyc + - name: brew install dependencies + run: brew install coreutils libressl + - name: Test with the most recent ponyc release + run: make test + - name: Bootstrap test + run: .ci-scripts/test-bootstrap.sh diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 22ad368..95e3100 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,7 +6,7 @@ on: - \d+.\d+.\d+ jobs: - create-and-upload-a-release: + x86-64-unknown-linux-release: name: Build and upload to Cloudsmith runs-on: ubuntu-latest container: @@ -18,6 +18,22 @@ jobs: env: CLOUDSMITH_API_KEY: ${{ secrets.CLOUDSMITH_API_KEY }} + x86-64-apple-darwin-release: + name: Build and upload x86-64-apple-darwin-release to Cloudsmith + runs-on: macos-latest + steps: + - uses: actions/checkout@v1 + - name: brew install ponyc + run: brew install ponyc + - name: brew install dependencies + run: brew install coreutils libressl python + - name: pip install dependencies + run: pip3 install --upgrade cloudsmith-cli + - name: Build and upload + run: bash .ci-scripts/release/x86-64-apple-darwin-release.bash + env: + CLOUDSMITH_API_KEY: ${{ secrets.CLOUDSMITH_API_KEY }} + build-release-docker-images: name: Build and push release Docker images runs-on: ubuntu-latest @@ -36,7 +52,7 @@ jobs: runs-on: ubuntu-latest container: image: ponylang/shared-docker-ci-release:20191107 - needs: [create-and-upload-a-release, build-release-docker-images] + needs: [x86-64-unknown-linux-release, x86-64-apple-darwin-release, build-release-docker-images] steps: - uses: actions/checkout@v1 - name: Trigger release announcement diff --git a/Makefile b/Makefile index 66d0003..ee1bcef 100644 --- a/Makefile +++ b/Makefile @@ -14,9 +14,9 @@ SRC_DIR ?= cmd binary := $(BUILD_DIR)/ponyup ifdef config - ifeq (,$(filter $(config),debug release)) - $(error Unknown configuration "$(config)") - endif + ifeq (,$(filter $(config),debug release)) + $(error Unknown configuration "$(config)") + endif endif ifeq ($(config),debug) @@ -24,29 +24,29 @@ ifeq ($(config),debug) endif ifeq ($(ssl), 1.1.x) - PONYC_FLAGS += -Dopenssl_1.1.x + PONYC_FLAGS += -Dopenssl_1.1.x else ifeq ($(ssl), 0.9.0) - PONYC_FLAGS += -Dopenssl_0.9.0 + PONYC_FLAGS += -Dopenssl_0.9.0 else - $(error Unknown SSL version "$(ssl)". Must set using 'ssl=FOO') + $(error Unknown SSL version "$(ssl)". Must set using 'ssl=FOO') endif ifneq ($(arch),) - PONYC_FLAGS += --cpu $(arch) + PONYC_FLAGS += --cpu $(arch) endif ifdef static - ifeq (,$(filter $(static),true false)) - $(error "static must be true or false) - endif + ifeq (,$(filter $(static),true false)) + $(error "static must be true or false) + endif endif ifeq ($(static),true) - LINKER += --static + LINKER += --static endif ifneq ($(linker),) - LINKER += --link-ldcmd=$(linker) + LINKER += --link-ldcmd=$(linker) endif # Default to version from `VERSION` file but allowing overridding on the @@ -55,16 +55,16 @@ endif # overridden version *should not* contain spaces or characters that aren't # legal in filesystem path names ifndef version - version := $(shell cat VERSION) - ifneq ($(wildcard .git),) - sha := $(shell git rev-parse --short HEAD) - tag := $(version)-$(sha) - else - tag := $(version) - endif + version := $(shell cat VERSION) + ifneq ($(wildcard .git),) + sha := $(shell git rev-parse --short HEAD) + tag := $(version)-$(sha) + else + tag := $(version) + endif else - foo := $(shell touch VERSION) - tag := $(version) + foo := $(shell touch VERSION) + tag := $(version) endif SOURCE_FILES := $(shell find $(SRC_DIR) -path $(SRC_DIR)/test -prune -o -name \*.pony) @@ -89,7 +89,8 @@ install: $(binary) SOURCE_FILES := $(shell find cmd -name \*.pony) test: $(binary) - ./test/test.sh + stable env ponyc $(PONYC_FLAGS) $(LINKER) test -o $(BUILD_DIR) -b test + $(BUILD_DIR)/test ${ponytest_args} clean: rm -rf $(BUILD_DIR) $(GEN_FILES) diff --git a/cmd/cli.pony b/cmd/cli.pony index 6687087..402fc98 100644 --- a/cmd/cli.pony +++ b/cmd/cli.pony @@ -46,7 +46,7 @@ primitive CLI "update", "Install or update a package", [ OptionSpec.string( - "libc", "Specify libc (gnu or musl)", None, "gnu") + "platform", "Specify platform (x86_64-linux-gnu)", None, "") ], [ ArgSpec.string("package") ArgSpec.string("version/channel") @@ -55,7 +55,7 @@ primitive CLI "select", "Select the default version for a package", [ OptionSpec.string( - "libc", "Specify libc (gnu or musl)", None, "gnu") + "platform", "Specify platform (x86_64-linux-gnu)", None, "") ], [ ArgSpec.string("package") ArgSpec.string("version") diff --git a/cmd/cloudsmith.pony b/cmd/cloudsmith.pony new file mode 100644 index 0000000..7cd798d --- /dev/null +++ b/cmd/cloudsmith.pony @@ -0,0 +1,32 @@ +use "json" + +primitive Cloudsmith + fun pkg_name(pkg: Package): String iso^ => + let name = pkg.string() + name.replace("-nightly", "") + name.replace("-release", "") + name.replace("-x86_64-", "-x86-64-") + name.replace("-linux", "-unknown-linux") + name.replace("-darwin", "-apple-darwin") + name + + fun repo_url(repo': String): String => + let repo_name = + match consume repo' + | "nightly" => "nightlies" + | "release" => "releases" + | let s: String => s + end + "".join( + [ "https://api.cloudsmith.io/packages/ponylang/"; repo_name; "/" + ].values()) + + fun query(pkg: Package): String => + let pkg_str = pkg_name(pkg) + pkg_str.replace("-" + pkg.version + "-", "%20") + if pkg.version != "latest" then pkg_str.append("%20" + pkg.version) end + "".join( + [ "?query="; consume pkg_str + "%20status:completed" + "&page=1&page_size=1" + ].values()) diff --git a/cmd/http_handlers.pony b/cmd/http_handlers.pony index d6adac0..8606dd3 100644 --- a/cmd/http_handlers.pony +++ b/cmd/http_handlers.pony @@ -2,6 +2,7 @@ use "collections" use "crypto" use "files" use "http" +use "json" use "net" class val HTTPGet @@ -33,9 +34,9 @@ class val HTTPGet class QueryHandler is HTTPHandler let _notify: PonyupNotify var _buf: String iso = recover String end - let _cb: {((String | None))} val + let _cb: {(Array[JsonObject val] iso)} val - new create(notify: PonyupNotify, cb: {((String | None))} val) => + new create(notify: PonyupNotify, cb: {(Array[JsonObject val] iso)} val) => _notify = notify _cb = cb @@ -56,7 +57,15 @@ class QueryHandler is HTTPHandler fun ref finished() => _notify.log(Extra, "received response of size " + _buf.size().string()) - _cb(_buf = recover String end) + let json_doc = recover trn JsonDoc end + let result = recover Array[JsonObject val] end + try + json_doc.parse(_buf = recover String end)? + for v in ((consume val json_doc).data as JsonArray val).data.values() do + result.push(v as JsonObject val) + end + end + _cb(consume result) fun failed( reason: (AuthFailed val | ConnectionClosed val | ConnectFailed val)) diff --git a/cmd/main.pony b/cmd/main.pony index a3f7037..e43d39f 100644 --- a/cmd/main.pony +++ b/cmd/main.pony @@ -104,7 +104,7 @@ actor Main is PonyupNotify command.arg("package").string(), chan(0)?, try chan(1)? else "latest" end, - command.option("libc").string())? + command.option("platform").string().split("-"))? else log(Err, "".join( [ "unexpected selection: " @@ -123,7 +123,7 @@ actor Main is PonyupNotify command.arg("package").string(), chan(0)?, try chan(1)? else "latest" end, - command.option("libc").string())? + command.option("platform").string().split("-"))? else log(Err, "".join( [ "unexpected selection: " diff --git a/cmd/package.pony b/cmd/package.pony deleted file mode 100644 index bc009b5..0000000 --- a/cmd/package.pony +++ /dev/null @@ -1,130 +0,0 @@ -use "json" - -interface val Package is Stringable - fun name(): String - fun repo(): String - fun version(): (String | None) - fun libc(): (String | None) - fun source_url(): String - fun query(): String - fun parse_sync(res: String): SyncInfo ? - fun string(): String iso^ - fun update_version(version': String): Package - -primitive Packages - fun apply(): Array[String] box => - ["changelog-tool"; "corral"; "ponyc"; "ponyup"; "stable"] - - fun from_string(str: String): Package ? => - let fragments = str.split("-") - match (fragments(0)?, fragments(1)?) - | ("changelog", "tool") => - fragments.delete(1)? - fragments(0)? = "changelog-tool" - end - from_fragments( - fragments(0)?, - fragments(1)?, - fragments(2)?, - try fragments(3)? end)? - - fun from_fragments( - name: String, - repo: String, - version: String, - libc: (String | None) = None) - : Package ? - => - let version' = if version != "latest" then consume version end - match repo - | "nightly" => Cloudsmith(name, "nightlies", version', libc) - | "release" => Cloudsmith(name, "releases", version', libc) - else error - end - - -class val Cloudsmith is Package - let _name: String - let _repo: String - let _version: (String | None) - let _libc: (String | None) - - new val create( - name': String, - repo': String, - version': (String | None) = None, - libc': (String | None) = None) - => - _name = name' - _repo = repo' - _version = version' - _libc = libc' - - fun name(): String => - _name - - fun repo(): String => - match _repo - | "nightlies" => "nightly" - | "releases" => "release" - else _repo - end - - fun version(): (String | None) => - _version - - fun libc(): (String | None) => - _libc - - fun source_url(): String => - "".join( - [ "https://api.cloudsmith.io/packages/ponylang/"; _repo; "/" - ].values()) - - fun query(): String => - "".join( - [ "?query="; _name - if _name == "ponyc" - then try "%20" + (_libc as String) else "" end - else "" - end - match _version - | let v: String => "%20" + v - | None => "" - end - "%20status:completed" - "&page=1&page_size=1" - ].values()) - - fun parse_sync(res: String): SyncInfo ? => - let json_doc = JsonDoc .> parse(res)? - let obj = (json_doc.data as JsonArray).data(0)? as JsonObject - SyncInfo( - obj.data("version")? as String, - obj.data("checksum_sha512")? as String, - obj.data("cdn_url")? as String) - - fun string(): String iso^ => - let fragments = - [ _name - repo() - match _version - | let v: String => v - | None => "latest" - end - ] - if _name == "ponyc" then try fragments.push(_libc as String) end end - "-".join(fragments.values()) - - fun update_version(version': String): Cloudsmith => - create(_name, _repo, version', _libc) - -class val SyncInfo - let version: String - let checksum: String - let download_url: String - - new val create(version': String, checksum': String, download_url': String) => - version = version' - checksum = checksum' - download_url = download_url' diff --git a/cmd/packages.pony b/cmd/packages.pony new file mode 100644 index 0000000..7953a19 --- /dev/null +++ b/cmd/packages.pony @@ -0,0 +1,101 @@ + +primitive Packages + fun apply(): Array[String] box => + ["changelog-tool"; "corral"; "ponyc"; "ponyup"; "stable"] + + fun from_fragments( + name: String, + channel: String, + version: String, + platform: Array[String] box) + : Package ? + => + var cpu: CPU = AMD64 + var os: OS = + if Platform.linux() then Linux + elseif Platform.osx() then Darwin + else error + end + var libc: Libc = + if (name == "ponyc") and (os is Linux) then Glibc + else None + end + for field in platform.values() do + match field + | "x86_64" | "x64" | "amd64" => cpu = AMD64 + | "linux" => os = Linux + | "darwin" => os = Darwin + | "gnu" => libc = Glibc + | "musl" => libc = Musl + | "none" | "unknown" | "pc" | "apple" => None + else error + end + end + if (os is Darwin) then libc = None end + Package._create(name, channel, version, (cpu, os, libc)) + + fun from_string(str: String): Package ? => + let fragments = str.split("-") + match (fragments(0)?, fragments(1)?) + | ("changelog", "tool") => + fragments.delete(1)? + fragments(0)? = "changelog-tool" + end + from_fragments( + fragments(0)?, + fragments(1)?, + fragments(2)?, + (consume fragments).slice(3))? + +class val Package + let name: String + let channel: String + let version: String + let cpu: CPU + let os: OS + let libc: Libc + + new val _create( + name': String, + channel': String, + version': String, + platform': (CPU, OS, Libc)) + => + name = name' + channel = channel' + version = version' + (cpu, os, libc) = platform' + + fun update_version(version': String): Package => + _create(name, channel, version', (cpu, os, libc)) + + fun platform(): String iso^ => + let fragments = Array[String] + match cpu + | AMD64 => fragments.push("x86_64") + end + match os + | Linux => fragments.push("linux") + | Darwin => fragments.push("darwin") + end + if name == "ponyc" then + match libc + | Glibc => fragments.push("gnu") + | Musl => fragments.push("musl") + end + end + "-".join(fragments.values()) + + fun string(): String iso^ => + "-".join([name; channel; version; platform()].values()) + +type CPU is AMD64 +primitive AMD64 + +type OS is (Linux | Darwin) +primitive Linux +primitive Darwin + +type Libc is (None | Glibc | Musl) +primitive Glibc +primitive Musl diff --git a/cmd/ponyup.pony b/cmd/ponyup.pony index 6ea9cd0..3151ad9 100644 --- a/cmd/ponyup.pony +++ b/cmd/ponyup.pony @@ -57,8 +57,8 @@ actor Ponyup return end - if not Packages().contains(pkg.name(), {(a, b) => a == b }) then - _notify.log(Err, "unknown package: " + pkg.name()) + if not Packages().contains(pkg.name, {(a, b) => a == b }) then + _notify.log(Err, "unknown package: " + pkg.name) return end @@ -68,28 +68,24 @@ actor Ponyup end _notify.log(Info, "updating " + pkg.string()) - _notify.log(Info, "syncing updates from " + pkg.source_url()) - let query_string = pkg.source_url() + pkg.query() + let src_url = Cloudsmith.repo_url(pkg.channel) + _notify.log(Info, "syncing updates from " + src_url) + let query_string = src_url + Cloudsmith.query(pkg) _notify.log(Extra, "query url: " + query_string) _http_get( query_string, {(_)(self = recover tag this end, pkg) => - QueryHandler(_notify, {(res) => self.query_response(pkg, res) }) + QueryHandler(_notify, {(res) => self.query_response(pkg, consume res) }) }) - be query_response(pkg: Package, res: (String | None)) => - let body = + be query_response(pkg: Package, res: Array[JsonObject val] iso) => + (let version, let checksum, let download_url) = try - res as String - else - _notify.log(Err, "unable to read response body") - return - end - - let sync_info = - try - pkg.parse_sync(body)? + res(0)? + ( res(0)?.data("version")? as String + , res(0)?.data("checksum_sha512")? as String + , res(0)?.data("cdn_url")? as String ) else _notify.log(Err, "".join( [ "requested package, "; pkg; ", was not found" @@ -97,7 +93,7 @@ actor Ponyup return end - let pkg' = (consume pkg).update_version(sync_info.version) + let pkg' = (consume pkg).update_version(version) if _lockfile.contains(pkg') then _notify.log(Info, pkg'.string() + " is up to date") @@ -112,8 +108,8 @@ actor Ponyup _notify.log(Err, "invalid path: " + _root.path + "/" + pkg'.string()) return end - _notify.log(Info, "pulling " + sync_info.version) - _notify.log(Extra, "download url: " + sync_info.download_url) + _notify.log(Info, "pulling " + version) + _notify.log(Extra, "download url: " + download_url) _notify.log(Extra, "install path: " + install_path.path) if (not _root.exists()) and (not _root.mkdir()) then @@ -124,12 +120,11 @@ actor Ponyup let dump = DLDump( _notify, dl_path, - {(checksum)(self = recover tag this end) => - self.dl_complete( - pkg', install_path, dl_path, sync_info.checksum, checksum) + {(checksum')(self = recover tag this end) => + self.dl_complete(pkg', install_path, dl_path, checksum, checksum') }) - _http_get(sync_info.download_url, {(_)(dump) => DLHandler(dump) }) + _http_get(download_url, {(_)(dump) => DLHandler(dump) }) be dl_complete( pkg: Package, @@ -159,7 +154,7 @@ actor Ponyup => dl_path.remove() _lockfile.add_package(pkg) - if _lockfile.selection(pkg.name()) is None then + if _lockfile.selection(pkg.name) is None then select(pkg) else _lockfile.dispose() @@ -174,7 +169,7 @@ actor Ponyup end _notify.log(Info, " ".join( - [ "selecting"; pkg; "as default for"; pkg.name() + [ "selecting"; pkg; "as default for"; pkg.name ].values())) try @@ -193,7 +188,7 @@ actor Ponyup return end - let link_rel: String = "/".join(["bin"; pkg.name()].values()) + let link_rel: String = "/".join(["bin"; pkg.name].values()) let bin_rel: String = "/".join([pkg.string(); link_rel].values()) try let bin_path = _root.join(bin_rel)? @@ -202,7 +197,7 @@ actor Ponyup let link_dir = _root.join("bin")? if not link_dir.exists() then link_dir.mkdir() end - let link_path = link_dir.join(pkg.name())? + let link_path = link_dir.join(pkg.name)? _notify.log(Info, "link: " + link_path.path) if link_path.exists() then link_path.remove() end @@ -304,21 +299,17 @@ class LockFile let pkg = Packages.from_string(fields(0)?)? let selected = try fields(1)? == "*" else false end - let entry = _entries.get_or_else(pkg.name(), LockFileEntry) + let entry = _entries.get_or_else(pkg.name, LockFileEntry) if selected then entry.selection = entry.packages.size() end entry.packages.push(pkg) - _entries(pkg.name()) = entry + _entries(pkg.name) = entry end fun contains(pkg: Package): Bool => - let v = - match pkg.version() - | let v: String => v - | None => return false - end - let entry = _entries.get_or_else(pkg.name(), LockFileEntry) + if pkg.version == "latest" then return false end + let entry = _entries.get_or_else(pkg.name, LockFileEntry) entry.packages.contains(pkg, {(a, b) => a.string() == b.string() }) fun selection(pkg_name: String): (Package | None) => @@ -328,12 +319,12 @@ class LockFile end fun ref add_package(pkg: Package) => - let entry = _entries.get_or_else(pkg.name(), LockFileEntry) + let entry = _entries.get_or_else(pkg.name, LockFileEntry) entry.packages.push(pkg) - _entries(pkg.name()) = entry + _entries(pkg.name) = entry fun ref select(pkg: Package) ? => - let entry = _entries(pkg.name())? + let entry = _entries(pkg.name)? entry.selection = entry.packages.find( pkg where predicate = {(a, b) => a.string() == b.string() })? @@ -386,41 +377,33 @@ actor ShowPackages if timeout == 0 then return end - for src in - [ Cloudsmith("", "nightlies") - Cloudsmith("", "releases") - ].values() - do - let query_string = src.source_url() + "?page=1&query=tag%3Alatest" + for repo in ["nightlies"; "releases"].values() do + let query_string = + Cloudsmith.repo_url(repo) + "?page=1&query=tag%3Alatest" _notify.log(Extra, "query url: " + query_string) _http_get( query_string, - {(_)(self = recover tag this end, src) => - QueryHandler(_notify, {(res) => - match res - | let body: String => - try - let json_doc = JsonDoc .> parse(body)? - let packages = recover Array[String] end - for v in (json_doc.data as JsonArray).data.values() do - let obj = v as JsonObject + {(_)(self = recover tag this end, repo) => + QueryHandler( + _notify, + {(res) => + let packages = recover Array[String] end + for obj in (consume res).values() do + try let filename = obj.data("filename")? as String var glibc: (String | None) = None if filename.contains("gnu") then glibc = "gnu" end if filename.contains("musl") then glibc = "musl" end packages.push(Packages.from_string("-".join( [ filename.split("-")(0)? - src.repo() + repo obj.data("version")? as String glibc ].values()))?.string()) end - self.append(consume packages) end - | None => - _notify.log(Err, "unable to read response body") - end - }) + self.append(consume packages) + }) }) end diff --git a/ponyup-init.sh b/ponyup-init.sh index 36c5786..8aa1a27 100755 --- a/ponyup-init.sh +++ b/ponyup-init.sh @@ -38,10 +38,18 @@ echo "ponyup_root = ${ponyup_root}" mkdir -p "${ponyup_root}/bin" platform_os=$(uname -s) -if [ "$(echo "${platform_os}" | cut -c1-5)" != "Linux" ]; then +case "${platform_os}" in +Linux*) + platform_os="unknown-linux" + ;; +Darwin*) + platform_os="apple-darwin" + ;; +*) echo "Unsupported OS: ${platform_os}" exit 1 -fi + ;; +esac platform_cpu=$(uname -m) case "${platform_cpu}" in @@ -55,7 +63,7 @@ case "${platform_cpu}" in esac query_url="https://api.cloudsmith.io/packages/ponylang/nightlies/" -query="?query=ponyup-${platform_cpu}&page=1&page_size=1" +query="?query=ponyup-${platform_cpu}-${platform_os}&page=1&page_size=1" response=$(curl --request GET "${query_url}${query}") if [ "${response}" = "[]" ]; then diff --git a/test/main.pony b/test/main.pony new file mode 100644 index 0000000..8a5f100 --- /dev/null +++ b/test/main.pony @@ -0,0 +1,202 @@ +use "files" +use "json" +use "net" +use "ponytest" +use "process" +use "../cmd" + +actor Main is TestList + new create(env: Env) => + try + let test_dir = FilePath(env.root as AmbientAuth, "./.pony_test")? + if test_dir.exists() then test_dir.remove() end + end + PonyTest(env, this) + + fun tag tests(test: PonyTest) => + for package in Packages().values() do + test(_TestSync(package)) + end + test(_TestSelect) + +class _TestSync is UnitTest + let _pkg_name: String + + new iso create(pkg_name: String) => + _pkg_name = pkg_name + + fun name(): String => + "sync - " + _pkg_name + + fun apply(h: TestHelper) ? => + let auth = h.env.root as AmbientAuth + _SyncTester(h, auth, _pkg_name) + h.long_test(30_000_000_000) + +class _TestSelect is UnitTest + let _ponyc_versions: Array[String] val = + ["release-0.33.1"; "release-0.33.0"] + + fun name(): String => + "select" + + fun apply(h: TestHelper) ? => + let platform = _TestPonyup.platform(h.env.vars) + let install_args: {(String): Array[String] val} val = + {(v) => ["update"; "ponyc"; v; "--platform=" + platform] } + + let link = + FilePath( + h.env.root as AmbientAuth, + "./.pony_test/select/ponyup/bin/ponyc")? + + let check = + {()? => + h.assert_true(link.canonical()?.path.contains(_ponyc_versions(0)?)) + _TestPonyup.exec( + h, "select", ["select"; "ponyc" ; _ponyc_versions(1)?], + {()? => + h.assert_true( + link.canonical()?.path.contains(_ponyc_versions(1)?)) + h.complete(true) + } val)? + } val + + _TestPonyup.exec( + h, "select", install_args(_ponyc_versions(0)?), + {()(check) => + try + _TestPonyup.exec( + h, "select", install_args(_ponyc_versions(1)?), + {()? => check()? } val)? + else + h.complete(false) + end + } val)? + + h.long_test(30_000_000_000) + +actor _SyncTester is PonyupNotify + let _h: TestHelper + let _auth: AmbientAuth + let _pkg_name: String + embed _pkgs: Array[Package] = [] + + new create(h: TestHelper, auth: AmbientAuth, pkg_name: String) => + _h = h + _auth = auth + _pkg_name = pkg_name + + let platform = _TestPonyup.platform(h.env.vars) + let http_get = HTTPGet(NetAuth(_auth), this) + for channel in ["nightly"; "release"].values() do + try + let pkg = Packages.from_fragments( + _pkg_name, channel, "latest", platform.split("-"))? + let query_string: String = + Cloudsmith.repo_url(channel).clone() + .> append(Cloudsmith.query(pkg)) + .> replace("page_size=1", "page_size=2") + log(Extra, "query url: " + query_string) + http_get( + query_string, + {(_)(self = recover tag this end, pkg) => + QueryHandler(self, {(res) => self.add_packages(pkg, consume res) }) + }) + end + end + + be add_packages(pkg: Package, res: Array[JsonObject val] iso) => + for obj in (consume res).values() do + try + let file = obj.data("filename")? as String + _pkgs.push(pkg.update_version(obj.data("version")? as String)) + end + end + if _pkgs.size() >= 3 then run() end + + be run() => + if _pkgs.size() == 0 then + _h.complete(true) + return + end + try + let pkg = _pkgs.shift()? + _h.env.out.print("sync -- " + pkg.string()) + _TestPonyup.exec( + _h, + pkg.name, + [ "update"; pkg.name; pkg.channel + "-" + pkg.version + "--platform=" + pkg.platform() + ], + {()(self = recover tag this end)? => + _TestPonyup.check_files(_h, pkg.name, pkg)? + self.run() + } val)? + else + _h.fail("exec error") + _h.complete(false) + end + + be log(level: LogLevel, msg: String) => + _h.env.out.print(msg) + + be write(str: String, ansi_color_code: String = "") => + _h.env.out.write(str) + +primitive _TestPonyup + fun platform(vars: Array[String] box): String => + let key = "PONYUP_PLATFORM" + var platform' = "" + for v in vars.values() do + if not v.contains(key) then continue end + platform' = v.substring(key.size().isize() + 1) + break + end + platform' + + fun ponyup_bin(auth: AmbientAuth): FilePath? => + FilePath(auth, "./build")? + .join(if Platform.debug() then "debug" else "release" end)? + .join("ponyup")? + + fun exec(h: TestHelper, dir: String, args: Array[String] val, cb: {()?} val) + ? + => + let auth = h.env.root as AmbientAuth + let bin = ponyup_bin(auth)? + let ponyup_monitor = ProcessMonitor( + auth, + auth, + object iso is ProcessNotify + fun stdout(process: ProcessMonitor ref, data: Array[U8] iso) => + h.log(String.from_array(consume data)) + + fun stderr(process: ProcessMonitor ref, data: Array[U8] iso) => + h.log(String.from_array(consume data)) + + fun failed(p: ProcessMonitor, err: ProcessError) => + h.fail("ponyup error") + + fun dispose(p: ProcessMonitor, exit: I32) => + h.assert_eq[I32](exit, 0) + try + cb()? + else + h.fail("exec callback") + h.complete(false) + end + end, + bin, + recover + [bin.path; "--prefix=./.pony_test/" + dir; "--verbose"] .> append(args) + end, + h.env.vars) + + ponyup_monitor.done_writing() + +fun check_files(h: TestHelper, dir: String, pkg: Package) ? => + let auth = h.env.root as AmbientAuth + let install_path = FilePath(auth, "./.pony_test")?.join(dir)?.join("ponyup")? + let bin_path = install_path.join(pkg.string())?.join("bin")?.join(pkg.name)? + h.assert_true(bin_path.exists()) diff --git a/test/test.sh b/test/test.sh deleted file mode 100755 index aaf1f6c..0000000 --- a/test/test.sh +++ /dev/null @@ -1,182 +0,0 @@ -#!/bin/sh - -set -o errexit -set -o nounset - -check_file() { - expected=$1 - - if [ ! -f "${expected}" ]; then - printf "\\033[1;91m ===> error:\\033[0m expected file not found: %s\n" \ - "${expected}" - exit 1 - fi -} - -check_output() { - cmd=$1 - expected=$2 - - tmp_file=$(mktemp) - ${cmd} | tee "${tmp_file}" - output=$(cat "${tmp_file}") - rm "${tmp_file}" - if ! echo "${output}" | grep -q "${expected}"; then - printf "\\033[1;91m ===> error:\\033[0m did not match \"%s\"\n" \ - "${expected}" - exit 1 - fi -} - -check_version() { - package=$1 - version=$2 - if [ "${package}" = "ponyc" ]; then - check_output "${prefix}/ponyup/bin/ponyc --version" "${version}" - else - check_output "${prefix}/ponyup/bin/${package} version" "${version}" - fi -} - -test_title() { - title=$1 - printf "\\033[1;32m========================================================\n" - printf " Test: %s\n" "${title}" - printf "========================================================\\033[0m\n" -} - -latest_versions() { - repo=$1 - package=$2 - count=${3:-2} - query_url="https://api.cloudsmith.io/packages/ponylang/${repo}/" - query="?query=${package}" - if [ "${package}" = "ponyc" ]; then query="${query}%20${libc}"; fi - query="${query}%20status:completed&page=1&page_size=${count}" - response=$(curl -s --request GET "${query_url}${query}") - echo "${response}" | - sed 's/, /\n/g' | - awk '/"version":/ {print $2}' | - sed 's/"//g' -} - -ponyup_package() { - package_name=$1 - channel=$2 - version=$3 - libc=$4 - package="${package_name}-${channel}-${version}" - if [ "${package_name}" = "ponyc" ]; then package="${package}-${libc}"; fi - echo "${package}" -} - -ponyup_bin=build/release/ponyup -version=$(cut -f 1 error:\\033[0m unexpected show output \n" - exit 1 -fi