diff --git a/scripts/build/build_cross.sh b/scripts/build/build_cross.sh deleted file mode 100755 index 70f67aed..00000000 --- a/scripts/build/build_cross.sh +++ /dev/null @@ -1,82 +0,0 @@ -#!/bin/sh -set -e - -docker build -t rust-cross-compiler - << 'EOF' -FROM rust:1.80 -RUN apt-get update && apt-get install -y \ - gcc-mingw-w64-x86-64 \ - gcc-x86-64-linux-gnu \ - libc6-dev-amd64-cross \ - clang \ - libssl-dev \ - wget \ - xz-utils \ - cmake \ - patch \ - libxml2-dev \ - llvm-dev \ - uuid-dev \ - libssl-dev \ - && rm -rf /var/lib/apt/lists/* - -# Install osxcross -RUN git config --global --add safe.directory '*' -RUN git clone https://github.com/tpoechtrager/osxcross /root/osxcross -WORKDIR /root/osxcross -RUN wget -nc https://github.com/phracker/MacOSX-SDKs/releases/download/11.3/MacOSX11.3.sdk.tar.xz -RUN mv MacOSX11.3.sdk.tar.xz tarballs/ -RUN UNATTENDED=yes OSX_VERSION_MIN=10.7 ./build.sh -ENV PATH="/root/osxcross/target/bin:$PATH" - -# Install targets -RUN rustup target add x86_64-unknown-linux-gnu \ - x86_64-pc-windows-gnu \ - x86_64-apple-darwin \ - aarch64-apple-darwin - -WORKDIR /app - -ENV CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER=x86_64-linux-gnu-gcc -ENV CARGO_TARGET_X86_64_APPLE_DARWIN_LINKER=x86_64-apple-darwin20.4-clang -ENV CARGO_TARGET_AARCH64_APPLE_DARWIN_LINKER=aarch64-apple-darwin20.4-clang -ENV CC_x86_64_apple_darwin=x86_64-apple-darwin20.4-clang -ENV CXX_x86_64_apple_darwin=x86_64-apple-darwin20.4-clang++ -ENV CC_aarch64_apple_darwin=aarch64-apple-darwin20.4-clang -ENV CXX_aarch64_apple_darwin=aarch64-apple-darwin20.4-clang++ - -RUN mkdir -p /root/.cargo && \ - echo '\ -[target.x86_64-unknown-linux-gnu]\n\ -linker = "x86_64-linux-gnu-gcc"\n\ -\n\ -[target.x86_64-pc-windows-gnu]\n\ -linker = "x86_64-w64-mingw32-gcc"\n\ -\n\ -[target.x86_64-apple-darwin]\n\ -linker = "x86_64-apple-darwin20.4-clang"\n\ -ar = "x86_64-apple-darwin20.4-ar"\n\ -\n\ -[target.aarch64-apple-darwin]\n\ -linker = "aarch64-apple-darwin20.4-clang"\n\ -ar = "aarch64-apple-darwin20.4-ar"\n\ -' > /root/.cargo/config.toml -EOF - -docker run -it --rm -v "$(pwd)":/app rust-cross-compiler /bin/sh -c ' -set -e -echo "Building for x86 Linux..." -cargo build --target x86_64-unknown-linux-gnu --release -echo "Building for x86 Windows..." -cargo build --target x86_64-pc-windows-gnu --release -echo "Building for x86 macOS..." -cargo build --target x86_64-apple-darwin --release -echo "Building for ARM macOS..." -cargo build --target aarch64-apple-darwin --release -' - -echo "Build process completed." -echo "Built libraries can be found at:" -echo "x86 Linux: target/x86_64-unknown-linux-gnu/release/" -echo "x86 Windows: target/x86_64-pc-windows-gnu/release/" -echo "x86 macOS: target/x86_64-apple-darwin/release/" -echo "ARM macOS: target/aarch64-apple-darwin/release/" diff --git a/scripts/build/build_cross.ts b/scripts/build/build_cross.ts new file mode 100755 index 00000000..e6639a15 --- /dev/null +++ b/scripts/build/build_cross.ts @@ -0,0 +1,179 @@ +#!/usr/bin/env -S deno run -A + +import { resolve } from "jsr:@std/path"; +import { ensureDir } from "jsr:@std/fs"; + +const REPO_DIR = resolve(import.meta.dirname!, "..", ".."); +const DOCKER_IMAGE = "rust-cross-compiler"; +const DOCKERFILE = ` +FROM rust:1.80 +RUN apt-get update && apt-get install -y \\ + gcc-mingw-w64-x86-64 \\ + gcc-x86-64-linux-gnu \\ + libc6-dev-amd64-cross \\ + clang \\ + libssl-dev \\ + wget \\ + xz-utils \\ + cmake \\ + patch \\ + libxml2-dev \\ + llvm-dev \\ + uuid-dev \\ + libssl-dev \\ + curl \\ + unzip \\ + && rm -rf /var/lib/apt/lists/* + +# Install Node.js LTS +RUN curl -fsSL https://deb.nodesource.com/setup_lts.x | bash - +RUN apt-get install -y nodejs + +# Install Yarn +RUN corepack enable +RUN corepack prepare yarn@stable --activate + +# Install Deno +RUN curl -fsSL https://deno.land/x/install/install.sh | sh +ENV PATH="/root/.deno/bin:$PATH" + +# Install osxcross +RUN git config --global --add safe.directory '*' +RUN git clone https://github.com/tpoechtrager/osxcross /root/osxcross +WORKDIR /root/osxcross +RUN wget -nc https://github.com/phracker/MacOSX-SDKs/releases/download/11.3/MacOSX11.3.sdk.tar.xz +RUN mv MacOSX11.3.sdk.tar.xz tarballs/ +RUN UNATTENDED=yes OSX_VERSION_MIN=10.7 ./build.sh +ENV PATH="/root/osxcross/target/bin:$PATH" + +# Install targets +RUN rustup target add x86_64-unknown-linux-gnu \\ + x86_64-pc-windows-gnu \\ + x86_64-apple-darwin \\ + aarch64-apple-darwin + +WORKDIR /app + +ENV CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER=x86_64-linux-gnu-gcc +ENV CARGO_TARGET_X86_64_APPLE_DARWIN_LINKER=x86_64-apple-darwin20.4-clang +ENV CARGO_TARGET_AARCH64_APPLE_DARWIN_LINKER=aarch64-apple-darwin20.4-clang +ENV CC_x86_64_apple_darwin=x86_64-apple-darwin20.4-clang +ENV CXX_x86_64_apple_darwin=x86_64-apple-darwin20.4-clang++ +ENV CC_aarch64_apple_darwin=aarch64-apple-darwin20.4-clang +ENV CXX_aarch64_apple_darwin=aarch64-apple-darwin20.4-clang++ + +RUN mkdir -p /root/.cargo && \\ + echo '\\ +[target.x86_64-unknown-linux-gnu]\\n\\ +linker = "x86_64-linux-gnu-gcc"\\n\\ +\\n\\ +[target.x86_64-pc-windows-gnu]\\n\\ +linker = "x86_64-w64-mingw32-gcc"\\n\\ +\\n\\ +[target.x86_64-apple-darwin]\\n\\ +linker = "x86_64-apple-darwin20.4-clang"\\n\\ +ar = "x86_64-apple-darwin20.4-ar"\\n\\ +\\n\\ +[target.aarch64-apple-darwin]\\n\\ +linker = "aarch64-apple-darwin20.4-clang"\\n\\ +ar = "aarch64-apple-darwin20.4-ar"\\n\\ +' > /root/.cargo/config.toml +`; + +async function buildDockerImage() { + console.log("Building Docker image..."); + const command = new Deno.Command("docker", { + args: ["build", "-t", DOCKER_IMAGE, "-"], + stdin: "piped", + }); + const process = command.spawn(); + const writer = process.stdin.getWriter(); + await writer.write(new TextEncoder().encode(DOCKERFILE)); + await writer.close(); + const { code } = await process.output(); + if (code !== 0) { + throw new Error("Docker build failed"); + } +} + +async function buildAndCopyCrossPlatform(outDir: string) { + console.log("Building and copying cross-platform..."); + await Deno.remove(outDir, { recursive: true }).catch(() => {}); + + const platforms = [ + // { + // name: "linux_x86_64", + // target: "x86_64-unknown-linux-gnu", + // files: ["rivet", "librivet_toolchain_ffi.so"], + // }, + // { + // name: "windows_x86_64", + // target: "x86_64-pc-windows-gnu", + // files: ["rivet.exe", "rivet_toolchain_ffi.dll"], + // }, + // { + // name: "macos_x86_64", + // target: "x86_64-apple-darwin", + // files: ["rivet", "librivet_toolchain_ffi.dylib"], + // }, + { + name: "macos_arm64", + target: "aarch64-apple-darwin", + files: ["rivet", "librivet_toolchain_ffi.dylib"], + }, + ]; + + for (const platform of platforms) { + console.log(`Building for ${platform.name}...`); + const command = new Deno.Command("docker", { + args: [ + "run", + "--rm", + "-v", + `${REPO_DIR}:/app`, + "-e", + `OVERRIDE_TARGET=${platform.target}`, + DOCKER_IMAGE, + "/bin/sh", + "-c", + `cargo build --manifest-path Cargo.toml --target ${platform.target} --release && chown -R ${Deno.uid()}:${Deno.gid()} /app/target`, + ], + stdin: "inherit", + stdout: "inherit", + stderr: "inherit", + }); + + const { code } = await command.output(); + + if (code !== 0) { + throw new Error(`Build failed for ${platform.name}`); + } + + for (const file of platform.files) { + const srcPath = resolve( + REPO_DIR, + "target", + platform.target, + "release", + file, + ); + const destPath = resolve(REPO_DIR, outDir, platform.name, file); + await ensureDir(resolve(REPO_DIR, outDir, platform.name)); + await Deno.copyFile(srcPath, destPath); + console.log(`Copied ${srcPath} to ${destPath}`); + } + + // Delete target if needed + if (Deno.env.get("CROSS_DELETE_TARGET") == "1") { + const targetPath = resolve(REPO_DIR, "target", platform.target); + await Deno.remove(targetPath, { recursive: true }); + console.log(`Deleted ${targetPath}`); + } + } +} + +export async function buildCross(outDir: string) { + await buildDockerImage(); + await buildAndCopyCrossPlatform(outDir); +} + diff --git a/scripts/build/pre_build.sh b/scripts/build/pre_build.sh deleted file mode 100755 index 42b2fce6..00000000 --- a/scripts/build/pre_build.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/sh -set -euf -o pipefail - -if [[ "$RUSTTARGET" == "x86_64-unknown-linux-musl" ]]; then - echo "Instaling deps for x86_64-unknown-linux-musl" - apk update - apk add --no-cache pkgconfig openssl-dev gcc perl -else - echo "Instaling deps" - apk update - apk add --no-cache pkgconfig gcc perl -fi - diff --git a/scripts/release/publish_cli_release.ts b/scripts/release/publish_cli_release.ts deleted file mode 100755 index 2f840117..00000000 --- a/scripts/release/publish_cli_release.ts +++ /dev/null @@ -1,54 +0,0 @@ -#!/usr/bin/env -S deno run --allow-net --allow-env --allow-read - -import { S3Bucket } from "https://deno.land/x/s3@0.5.0/mod.ts"; -import { assert } from "https://deno.land/std@0.182.0/testing/asserts.ts"; - -const BUCKET_NAME = "rivet-releases"; -const BUCKET_FOLDER = "cli"; - -async function publishRelease(releaseName: string) { - const bucket = new S3Bucket({ - accessKeyID: Deno.env.get("AWS_ACCESS_KEY_ID")!, - secretKey: Deno.env.get("AWS_SECRET_ACCESS_KEY")!, - bucket: BUCKET_NAME, - region: "auto", - endpointURL: "https://2a94c6a0ced8d35ea63cddc86c2681e7.r2.cloudflarestorage.com/rivet-releases", - }); - - const files = [ - { - name: "rivet-cli-aarch64-mac", - path: "target/aarch64-apple-darwin/release/rivet", - }, - { - name: "rivet-cli-x86-linux", - path: "target/x86_64-unknown-linux-gnu/release/rivet", - }, - { - name: "rivet-cli-x86-mac", - path: "target/x86_64-apple-darwin/release/rivet", - }, - { - name: "rivet-cli-x86-windows.exe", - path: "target/x86_64-pc-windows-gnu/release/rivet.exe", - }, - ]; - - for (const { name, path } of files) { - const objectKey = `${BUCKET_FOLDER}/v${releaseName}/${name}`; - - console.log(`Uploading ${name} to ${BUCKET_NAME}/${objectKey}...`); - await bucket.putObject(objectKey, await Deno.readFile(path)); - console.log(`Uploaded ${name}`); - } - - console.log(`Finished publishing release ${releaseName}.`); -} - -const release = Deno.env.get("RELEASE_NAME"); -if (!release) { - console.error("Please provide a release name via the RELEASE_NAME environment variable"); - Deno.exit(1); -} - -await publishRelease(release); diff --git a/scripts/release/release.sh b/scripts/release/release.sh deleted file mode 100755 index d21a094e..00000000 --- a/scripts/release/release.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/sh -set -euf - -# To do a release, first, merge the Release PR created by release-please. You -# should be able to see the open PR at this link: -# -# https://github.com/rivet-gg/cli/labels/autorelease%3A%20pending -# -# Once the PR is merged, you can run this script with the version that was shown -# in the PR title. For example, the following PR was 1.2.0: -# -# https://github.com/rivet-gg/cli/pull/245 -# -# So you would run: -# ./scripts/release.sh 1.2.0 -# -# That will run CI that will create the release. This will also run a job that -# will change the label of the release-please PR, since it's not done -# automatically. After the release is created, you'll need to copy items from -# the changelog into the release description: -# -# https://github.com/rivet-gg/cli/releases/edit/v1.1.0 -cargo release --package rivet-cli --execute --no-publish --tag-prefix "" "$1" diff --git a/scripts/release/release.ts b/scripts/release/release.ts new file mode 100755 index 00000000..4ce8e493 --- /dev/null +++ b/scripts/release/release.ts @@ -0,0 +1,99 @@ +#!/usr/bin/env -S deno run -A + +import { resolve } from "jsr:@std/path"; +import { assert } from "jsr:@std/assert"; +import { S3Bucket } from "https://deno.land/x/s3@0.5.0/mod.ts"; +import { buildCross } from "../build/build_cross.ts"; + +function getRequiredEnvVar(name: string): string { + const value = Deno.env.get(name); + if (!value) { + throw new Error(`Required environment variable ${name} is not set`); + } + return value; +} + +const toolchainVersion = getRequiredEnvVar("TOOLCHAIN_VERSION"); +const awsAccessKeyId = getRequiredEnvVar("AWS_ACCESS_KEY_ID"); +const awsSecretAccessKey = getRequiredEnvVar("AWS_SECRET_ACCESS_KEY"); + +assert(/^v(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/.test(toolchainVersion), "ASSET_VERSION must be a valid semantic version starting with 'v'"); + +const OUTPUT_DIR = Deno.env.get("OUTPUT_DIR") ?? await Deno.makeTempDir({ prefix: "toolchain-" }); +console.log("Work dir:", OUTPUT_DIR); +const DIST_DIR = resolve(OUTPUT_DIR, "dist"); + +async function buildCrossPlatform() { + // We build this in the repo dir in order to make sure we use the build cache + console.log("Building cross-platform binaries"); + try { + await buildCross(DIST_DIR); + } catch (err) { + throw new Error(`Failed to build cross-platform binaries: ${err}`); + } +} + +async function generateZipFiles(): Promise { + console.log("Generating zip files"); + const zipPaths: string[] = []; + for await (const entry of Deno.readDir(DIST_DIR)) { + if (entry.isDirectory) { + const folderName = entry.name; + const folderPath = resolve(DIST_DIR, folderName); + for await (const file of Deno.readDir(folderPath)) { + if (file.isFile) { + const fileName = file.name; + const zipPath = resolve(OUTPUT_DIR, `${folderName}-${fileName}.zip`); + const zipOutput = await (new Deno.Command("zip", { + args: ["-j", zipPath, resolve(folderPath, fileName)], + stdout: "inherit", + stderr: "inherit", + })).output(); + assert(zipOutput.success, `Failed to create zip for ${folderName}/${fileName}`); + console.log(`Zip file created: ${zipPath}`); + zipPaths.push(zipPath); + } + } + } + } + return zipPaths; +} + +async function uploadZipsToS3(zipPaths: string[]): Promise<{ zipUrls: string[] }> { + console.log("Uploading zip files to S3"); + const bucket = new S3Bucket({ + accessKeyID: awsAccessKeyId, + secretKey: awsSecretAccessKey, + bucket: "rivet-releases", + region: "auto", + endpointURL: "https://2a94c6a0ced8d35ea63cddc86c2681e7.r2.cloudflarestorage.com/rivet-releases", + }); + + const zipUrls: string[] = []; + + for (const zipPath of zipPaths) { + const fileName = zipPath.split("/").pop()!; + const [folderName, ...rest] = fileName.split("-"); + const zipObjectKey = `toolchain/${toolchainVersion}/${folderName}/${rest.join("-")}`; + + const zipFileData = await Deno.readFile(zipPath); + + await bucket.putObject(zipObjectKey, zipFileData); + + console.log(`Uploaded zip file to S3: ${zipObjectKey}`); + zipUrls.push(`https://releases.rivet.gg/${zipObjectKey}`); + } + + return { zipUrls }; +} + +async function main() { + await buildCrossPlatform(); + const zipPaths = await generateZipFiles(); + const { zipUrls } = await uploadZipsToS3(zipPaths); + console.log("Uploaded zip URLs:", zipUrls); +} + +if (import.meta.main) { + main(); +}