diff --git a/.github/bin/constants.sh b/.github/bin/constants.sh index 6ea3a71055..f1e1fe402d 100755 --- a/.github/bin/constants.sh +++ b/.github/bin/constants.sh @@ -1,5 +1,15 @@ #!/bin/bash +# shellcheck disable=SC2034 +solution="Lib9c" +projects=( + "Lib9c" +) +configuration=Release +executables=( + ".Lib9c.StateService" +) + # https://docs.microsoft.com/en-us/dotnet/core/rid-catalog rids=(linux-x64 osx-x64 osx-arm64 win-x64) @@ -8,7 +18,9 @@ rids=(linux-x64 osx-x64 osx-arm64 win-x64) # shellcheck disable=SC2235 if [ "$GITHUB_REPOSITORY" = "planetarium/lib9c" ] && [[ \ "$GITHUB_REF" = refs/tags/* || \ - "$GITHUB_REF" = refs/heads/main + "$GITHUB_REF" = refs/heads/main || \ + "$GITHUB_REF" = refs/heads/development || \ + "$GITHUB_REF" = refs/heads/release/* ]]; then publish_package=true fi diff --git a/.github/bin/dist-nuget.sh b/.github/bin/dist-nuget.sh new file mode 100755 index 0000000000..38fb26f0df --- /dev/null +++ b/.github/bin/dist-nuget.sh @@ -0,0 +1,40 @@ +#!/bin/bash +# Submit .nupkg files to NuGet. +# Note that this script is intended to be run by GitHub Actions. +set -e + +# shellcheck source=constants.sh +. "$(dirname "$0")/constants.sh" + +if [ ! -f obj/package_version.txt ]; then + { + echo "obj/package_version.txt file is missing." + echo "dist:version action must be run first." + } > /dev/stderr + exit 1 +fi + +if [ "$NUGET_API_KEY" = "" ]; then + echo "This script requires NUGET_API_KEY envrionment variable." > /dev/stderr + exit 1 +fi + +if [ "$publish_package" = "" ]; then + function dotnet-nuget { + echo "DRY-RUN: dotnet nuget" "$@" + } +else + function dotnet-nuget { + dotnet nuget "$@" + } +fi + +package_version="$(cat obj/package_version.txt)" + +for project in "${projects[@]}"; do + dotnet-nuget push \ + "./$project/bin/$configuration/$project.$package_version.nupkg" \ + --skip-duplicate \ + --api-key "$NUGET_API_KEY" \ + --source https://api.nuget.org/v3/index.json +done \ No newline at end of file diff --git a/.github/bin/dist-pack.sh b/.github/bin/dist-pack.sh index 08b55a3fd4..4efe01a093 100755 --- a/.github/bin/dist-pack.sh +++ b/.github/bin/dist-pack.sh @@ -1,27 +1,87 @@ #!/bin/bash +# Build a .nupkg file. +# Note that this script is intended to be run by GitHub Actions. set -e +# shellcheck source=constants.sh . "$(dirname "$0")/constants.sh" -for rid in "${rids[@]}"; do - output_dir="./Release/$rid/" - mkdir -p "$output_dir" +if ! (env | grep '^GITHUB_'); then + echo "This script is intended to be run by GitHub Actions." > /dev/stderr + exit 1 +fi - dotnet publish \ - -c Release \ - -r $rid \ - -o $output_dir \ - --self-contained \ - --version-suffix "$(git -C lib9c rev-parse HEAD)" +version="$(cat obj/package_version.txt)" # e.g. 0.50.0-dev.20230221015836 +version_prefix="$(cat obj/version_prefix.txt)" # e.g. 0.50.0 +if [[ -f obj/version_suffix.txt ]]; then # e.g. dev.20230221015836+35a2dbc + version_suffix="$(cat obj/version_suffix.txt)" +fi - bin_name=lib9c +for project in "${executables[@]}"; do + for rid in "${rids[@]}"; do + output_dir="./$project/bin/$configuration/$rid/" + mkdir -p "$output_dir" + dotnet publish \ + --runtime "$rid" \ + -p:PublishSingleFile=true \ + --self-contained \ + -p:Version="$version" \ + --configuration "$configuration" \ + --output "$output_dir" \ + "$project" || \ + if [[ "$?" = "139" ]]; then + # On GitHub Actions, `dotnet` command occasionally fails due to + # segfault. + dotnet publish \ + --runtime "$rid" \ + -p:PublishSingleFile=true \ + --self-contained \ + -p:Version="$version" \ + --configuration "$configuration" \ + --output "$output_dir" \ + "$project" + else + exit 1 + fi + bin_name="$(find "$output_dir" -type f -perm /o+x -exec basename {} \;)" pushd "$output_dir" - if [[ "$rid" = win-* ]]; then - zip -r9 "../${bin_name%.exe}-$rid.zip" ./* + zip -r9 "../${bin_name%.exe}-$version-$rid.zip" ./* else - tar cvfJ "../$bin_name-$rid.tar.xz" ./* + tar cvfJ "../$bin_name-$version-$rid.tar.xz" ./* fi popd rm -rf "$output_dir" + done +done + +for project in "${projects[@]}"; do + if [[ "$version_suffix" = "" ]]; then + dotnet_args="-p:Version=$version" + else + dotnet_args="-p:VersionPrefix=$version_prefix" \ + dotnet_args="$dotnet_args --version-suffix=$version_suffix" + dotnet_args="$dotnet_args -p:NoPackageAnalysis=true" + fi + # shellcheck disable=SC2086 + dotnet build -c "$configuration" $dotnet_args || \ + if [[ "$?" = "139" ]]; then + # On GitHub Actions, `dotnet` command occasionally fails due to segfault. + dotnet build -c "$configuration" $dotnet_args + else + exit 1 + fi + # shellcheck disable=SC2086 + dotnet pack "$project" -c "$configuration" $dotnet_args || \ + if [[ "$?" = "139" ]]; then + # On GitHub Actions, `dotnet` command occasionally fails due to segfault. + dotnet pack "$project" -c "$configuration" $dotnet_args + else + exit 1 + fi + + ls -al "./$project/bin/$configuration/" + if [ "$version" != "$version_prefix" ]; then + rm -f "./$project/bin/$configuration/$project.$version_prefix.nupkg" + fi done diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 097bb1501e..07cc02b29b 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -2,8 +2,11 @@ name: release on: push: + branches: + - "main" + - "development" tags: - - v* + - "*" jobs: release: @@ -18,7 +21,40 @@ jobs: - uses: actions/setup-dotnet@v3 with: dotnet-version: 6.0.x + - uses: actions/setup-node@v3 + with: + node-version: 'lts/*' + - id: determine-version + run: node scripts/determine-version.js + - shell: bash + run: | + mkdir -p obj + echo "$VERSION_PREFIX" > obj/version_prefix.txt + if [[ "$VERSION_SUFFIX" != "" ]]; then + echo "$VERSION_SUFFIX" > obj/version_suffix.txt + fi + echo "$PACKAGE_VERSION" > obj/package_version.txt + echo "$VERSION_TYPE" > obj/version_type.txt + env: + VERSION_PREFIX: ${{ steps.determine-version.outputs.version-prefix }} + VERSION_SUFFIX: ${{ steps.determine-version.outputs.version-suffix }} + PACKAGE_VERSION: ${{ steps.determine-version.outputs.package-version }} + VERSION_TYPE: ${{ steps.determine-version.outputs.version-type }} - run: .github/bin/dist-pack.sh + - run: | + . .github/bin/constants.sh + mkdir -p /tmp/dist-bin/ + for project in "${projects[@]}"; do + cp -r "$project/bin/$configuration"/* /tmp/dist-bin/ + done + - uses: actions/upload-artifact@main + with: + name: dist-bin + path: /tmp/dist-bin/ - run: .github/bin/dist-github-release.sh env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - if: env.NUGET_API_KEY != '' + run: .github/bin/dist-nuget.sh + env: + NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} diff --git a/Lib9c/Lib9c.csproj b/Lib9c/Lib9c.csproj index 9fdfd5ab1d..9f1713b993 100644 --- a/Lib9c/Lib9c.csproj +++ b/Lib9c/Lib9c.csproj @@ -9,6 +9,7 @@ .obj Nekoyume 8 + 0.4.0 true Debug;Release AnyCPU diff --git a/scripts/.editorconfig b/scripts/.editorconfig new file mode 100644 index 0000000000..7690e84936 --- /dev/null +++ b/scripts/.editorconfig @@ -0,0 +1,6 @@ +[*.js] +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 2 +max_line_length = 80 diff --git a/scripts/determine-version.js b/scripts/determine-version.js new file mode 100644 index 0000000000..b1dbf95c76 --- /dev/null +++ b/scripts/determine-version.js @@ -0,0 +1,144 @@ +#!/usr/bin/env node +// This script is used to determine the version of the current build. +// Intended to be run on CI, but can be run locally as well. +const fs = require("node:fs").promises; +const path = require("node:path"); +const util = require("node:util"); +const execFile = util.promisify(require("node:child_process").execFile); + +async function readVersionPrefix(xmlFile) { + // TODO: Use proper XML parser... + const xml = await fs.readFile(xmlFile, "utf8"); + const pattern = /([0-9]+\.[0-9]+\.[0-9]+)<\/VersionPrefix>/g; + const match = pattern.exec(xml); + if (!match) { + throw new Error(`Could not determine version prefix from ${xmlFile}`); + } + return match[1]; +} + +async function getCommitHash() { + if (process.env.GITHUB_SHA) { + return process.env.GITHUB_SHA; + } + const { stdout } = await execFile("git", ["rev-parse", "HEAD"]); + return stdout.trim(); +} + +async function getCommitTimestamp() { + let timestamp; + if (process.env.GITHUB_EVENT_PATH) { + const event = JSON.parse(await fs.readFile(process.env.GITHUB_EVENT_PATH)); + timestamp = event.head_commit.timestamp; + } + const { stdout } = await execFile("git", [ + "show", + "--no-patch", + "--format=%cI", + "HEAD", + ]); + timestamp = stdout.trim(); + if (timestamp) return new Date(timestamp); + return new Date(); +} + +async function getTag() { + if (process.env.GITHUB_REF_TYPE === "branch") return null; + if (process.env.GITHUB_REF_TYPE === "tag" && process.env.GITHUB_REF_NAME) { + return process.env.GITHUB_REF_NAME; + } + try { + const { stdout } = await execFile("git", [ + "describe", + "--exact-match", + "--tags", + "HEAD", + ]); + } catch (e) { + return null; + } + const tag = stdout.trim(); + if (tag) return tag; + return null; +} + +function getScheduledJobDate() { + if (process.env.GITHUB_EVENT_NAME?.startsWith("schedule")) { + // TODO: Read the date from the event payload for determinism. + const now = new Date(); + return `${now.getUTCFullYear()}${ + now.getUTCMonth() + 1 + }${now.getUTCDate()}}`; + } + return null; +} + +async function main() { + const csprojPath = path.join( + path.dirname(__dirname), + "Lib9c", + "Lib9c.csproj", + ); + const versionPrefix = await readVersionPrefix(csprojPath); + const scheduledJobDate = getScheduledJobDate(); + const tag = await getTag(); + const commitHash = (await getCommitHash()).substring(0, 7); + let packageVersion; + let versionSuffix; + let versionType; + if (scheduledJobDate != null) { + // Nightly + versionSuffix = `nightly.${scheduledJobDate}`; + packageVersion = `${versionPrefix}-${versionSuffix}`; + versionSuffix += `+${commitHash}`; + versionType = "nightly"; + } else if (tag != null) { + // Release + if (tag !== versionPrefix) { + console.error( + `Git tag (${tag}) does not match VersionPrefix (${versionPrefix})`, + ); + process.exit(1); + } + packageVersion = tag; + versionType = "stable"; + } else { + // Dev + const timestamp = await getCommitTimestamp(); + const ts = `${timestamp.getUTCFullYear()}${ + timestamp.getUTCMonth() + 1 + }${timestamp.getUTCDate()}${timestamp.getUTCHours()}${ + timestamp.getUTCMinutes() + 0 + }${timestamp.getUTCSeconds()}`; + versionSuffix = `dev.${ts}`; + packageVersion = `${versionPrefix}-${versionSuffix}`; + versionSuffix += `+${commitHash}`; + versionType = "dev"; + } + console.error("VersionPrefix:", versionPrefix); + if (versionSuffix) console.error("VersionSuffix:", versionSuffix); + console.error("PackageVersion:", packageVersion); + console.error("VersionType:", versionType); + if (process.env.GITHUB_OUTPUT) { + // https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#environment-files + await fs.appendFile( + process.env.GITHUB_OUTPUT, + `version-prefix=${versionPrefix}\n`, + ); + if (versionSuffix) + await fs.appendFile( + process.env.GITHUB_OUTPUT, + `version-suffix=${versionSuffix}\n`, + ); + await fs.appendFile( + process.env.GITHUB_OUTPUT, + `package-version=${packageVersion}\n`, + ); + await fs.appendFile( + process.env.GITHUB_OUTPUT, + `version-type=${versionType}\n`, + ); + } +} + +main();