diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a11c81e..775376a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,14 +18,20 @@ jobs: steps: - uses: actions/checkout@v1 - - uses: xmake-io/github-action-setup-xmake@test + - uses: xmake-io/github-action-setup-xmake@cache with: xmake-version: ${{ matrix.version }} actions-cache-folder: '.xmake-cache' + actions-cache-key: ${{ matrix.version }} + build-cache: true + build-cache-key: ${{ matrix.version }} + package-cache: true + package-cache-key: ${{ matrix.version }} + project-path: 'tests/sample' - name: Run tests run: | xmake --version - xmake create -P test - xmake build -P test - xmake run -P test + cd tests/sample + xmake -y -vD + xmake run diff --git a/README.md b/README.md index c66f3d5..1dda5eb 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ See [action.yml](./action.yml). ## Example -Use latest version: +### Use latest version ```yml uses: xmake-io/github-action-setup-xmake@v1 @@ -19,7 +19,7 @@ with: xmake-version: latest ``` -Use specified version: +### Use specified version ```yml uses: xmake-io/github-action-setup-xmake@v1 @@ -27,7 +27,7 @@ with: xmake-version: '2.5.3' ``` -Use specified branch: +### Use specified branch ```yml uses: xmake-io/github-action-setup-xmake@v1 @@ -35,7 +35,7 @@ with: xmake-version: branch@master ``` -Use semver: +### Use semver ```yml uses: xmake-io/github-action-setup-xmake@v1 @@ -43,25 +43,74 @@ with: xmake-version: '>=2.2.6 <=2.5.3' ``` -Use action cache: +### Cache xmake ```yml uses: xmake-io/github-action-setup-xmake@v1 with: - xmake-version: '2.7.2' + xmake-version: '2.9.7' actions-cache-folder: '.xmake-cache' ``` -Use action cache with cachekey: +### Cache xmake with cachekey ```yml uses: xmake-io/github-action-setup-xmake@v1 with: - xmake-version: '2.7.2' + xmake-version: '2.9.7' actions-cache-folder: '.xmake-cache' actions-cache-key: 'archlinux-ci' ``` +### Cache packages + +```yml +uses: xmake-io/github-action-setup-xmake@v1 +with: + xmake-version: '2.9.7' + package-cache: true + package-cache-key: 'archlinux-ci' + project-path: 'myproject' # we need to compute packages hashkey for project +``` + +### Cache build + +By default, xmake disables build cache when building on ci, so we need to enable it first. + +```bash +$ xmake f --policies=build.ccache:y +``` + +And xmake v2.9.8 will enable it by default if action/build-cache is enabled. + +```yml +uses: xmake-io/github-action-setup-xmake@v1 +with: + xmake-version: '2.9.7' + build-cache: true + build-cache-key: 'archlinux-ci' +``` + +Cache build with the specific project path. + +```yml +uses: xmake-io/github-action-setup-xmake@v1 +with: + xmake-version: '2.9.7' + build-cache: true + project-path: 'myproject' # we can get the build cache path from project. +``` + +Cache build with the specific build path. + +```yml +uses: xmake-io/github-action-setup-xmake@v1 +with: + xmake-version: '2.9.7' + build-cache: true + build-cache-path: 'build/.build_cache' +``` + ## Contributing ### Prepare development environment diff --git a/action.yml b/action.yml index 492fe77..4e56352 100644 --- a/action.yml +++ b/action.yml @@ -16,6 +16,26 @@ inputs: actions-cache-key: description: The actions cache key. default: '' + package-cache: + description: Enable package cache. + default: false + package-cache-key: + description: The packages cache key. + default: '' + project-path: + description: The project root path. + default: '' + build-cache: + description: Enable build cache. + default: false + build-cache-path: + description: The build cache path. + default: '' + build-cache-key: + description: The build cache key. + default: '' + runs: using: 'node20' main: 'dist/index.js' + post: 'dist/index.js' diff --git a/dist/index.js b/dist/index.js index 71958c4..1ddc108 100644 --- a/dist/index.js +++ b/dist/index.js @@ -82448,6 +82448,199 @@ try { } catch (er) {} +/***/ }), + +/***/ 53: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.saveBuildCache = exports.loadBuildCache = void 0; +const core = __nccwpck_require__(9093); +const exec_1 = __nccwpck_require__(7775); +const io = __nccwpck_require__(2826); +const cache = __nccwpck_require__(6878); +const os = __nccwpck_require__(2037); +const path = __nccwpck_require__(1017); +const fsutils = __nccwpck_require__(4295); +function getBuildTime(hours) { + let key = 'BuildTime'; + if (hours && hours !== '') { + key = key + hours; + } + let buildTime = core.getState(key); + if (!buildTime || buildTime === '') { + const now = new Date(); + const year = now.getFullYear(); + const month = String(now.getMonth() + 1).padStart(2, '0'); + const day = String(now.getDate()).padStart(2, '0'); + if (!hours || hours === '') { + hours = String(now.getHours()).padStart(2, '0'); + } + buildTime = `${year}${month}${day}_${hours}`; + core.saveState(key, buildTime); + } + return buildTime; +} +function getProjectRootPath() { + let projectRootPath = core.getInput('project-path'); + if (!projectRootPath) { + projectRootPath = process.cwd(); + } + projectRootPath = projectRootPath.trim(); + if (projectRootPath && projectRootPath !== '' && !path.isAbsolute(projectRootPath)) { + projectRootPath = path.join(process.cwd(), projectRootPath); + } + return projectRootPath; +} +function getBuildCacheKey(buildCacheTime) { + var _a; + let buildCacheKey = core.getInput('build-cache-key'); + if (!buildCacheKey) { + buildCacheKey = ''; + } + if (!buildCacheTime || buildCacheTime === '') { + buildCacheTime = getBuildTime(); + } + return `xmake-build-cache-${buildCacheKey}-${buildCacheTime}-${os.arch()}-${os.platform()}-${(_a = process.env.RUNNER_OS) !== null && _a !== void 0 ? _a : 'unknown'}`; +} +async function getBuildCachePath() { + let buildCachePath = core.getInput('build-cache-path'); + if (!buildCachePath) { + buildCachePath = ''; + const projectRootPath = getProjectRootPath(); + if (projectRootPath && projectRootPath !== '' && fsutils.isDir(projectRootPath)) { + const options = {}; + options.cwd = projectRootPath; + options.listeners = { + stdout: (data) => { + buildCachePath += data.toString(); + }, + }; + await (0, exec_1.exec)('xmake', [ + 'l', + '-c', + 'import("core.project.config"); import("private.cache.build_cache"); config.load(); print(build_cache.rootdir())', + ], options); + buildCachePath = buildCachePath.trim(); + if (buildCachePath !== '' && !path.isAbsolute(buildCachePath)) { + buildCachePath = path.join(projectRootPath, buildCachePath); + } + } + else { + buildCachePath = 'build/.build_cache'; + } + } + return buildCachePath; +} +function getBuildCacheFolder() { + return '.xmake-build-cache'; +} +async function loadBuildCache() { + const buildCache = core.getBooleanInput('build-cache'); + if (!buildCache) { + return; + } + // export $XMAKE_ACTION_BUILD_CACHE, xmake will check it and enable build cache by default on ci. + core.exportVariable('XMAKE_ACTION_BUILD_CACHE', 'true'); + const buildCacheFolder = getBuildCacheFolder(); + const buildCachePath = await getBuildCachePath(); + if (buildCacheFolder && process.env.GITHUB_WORKSPACE) { + const fullCachePath = path.join(process.env.GITHUB_WORKSPACE, buildCacheFolder); + const filepath = path.join(fullCachePath, 'build_cache_saved.txt'); + // Since action/cache cannot overwrite updates, we try to restore the cache from the last 24 hours. + const now = new Date(); + for (let i = 0; i < 24; i++) { + const hours = now.getHours() - i; + if (hours < 0) { + break; + } + const buildCacheKey = getBuildCacheKey(getBuildTime(String(hours).padStart(2, '0'))); + if (!fsutils.isFile(filepath)) { + core.info(`Restore build cache path: ${fullCachePath} to ${buildCachePath}, key: ${buildCacheKey}`); + await cache.restoreCache([buildCacheFolder], buildCacheKey); + } + if (fsutils.isFile(filepath)) { + if (fsutils.isDir(buildCachePath)) { + await io.rmRF(buildCachePath); + } + await io.cp(fullCachePath, buildCachePath, { + recursive: true, + }); + /* Even if the historical cache is hit, we need to update to the latest cache + * Therefore, only when the latest cache is hit, it is a real hit, and we no longer need to update the cache. + */ + if (i === 0) { + core.saveState('hitBuildCache', 'true'); + } + break; + } + } + if (!fsutils.isFile(filepath)) { + core.warning(`No cached files found at path "${fullCachePath}".`); + await io.rmRF(fullCachePath); + } + } +} +exports.loadBuildCache = loadBuildCache; +async function saveBuildCache() { + const buildCache = core.getBooleanInput('build-cache'); + if (!buildCache) { + return; + } + const buildCacheFolder = getBuildCacheFolder(); + const buildCacheKey = getBuildCacheKey(); + const buildCachePath = await getBuildCachePath(); + const hitBuildCache = !!core.getState('hitBuildCache'); + if (!hitBuildCache && buildCacheFolder && process.env.GITHUB_WORKSPACE && fsutils.isDir(buildCachePath)) { + const fullCachePath = path.join(process.env.GITHUB_WORKSPACE, buildCacheFolder); + core.info(`Save build cache path: ${buildCachePath} to ${fullCachePath}, key: ${buildCacheKey}`); + await io.cp(buildCachePath, fullCachePath, { + recursive: true, + }); + await (0, exec_1.exec)('xmake', ['l', 'os.touch', path.join(fullCachePath, 'build_cache_saved.txt')]); + await cache.saveCache([buildCacheFolder], buildCacheKey); + } +} +exports.saveBuildCache = saveBuildCache; + + +/***/ }), + +/***/ 4295: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.isDir = exports.isFile = void 0; +const fs = __nccwpck_require__(7147); +function isFile(filepath) { + try { + fs.accessSync(filepath, fs.constants.F_OK); + return true; + } + catch (_a) { + return false; + } +} +exports.isFile = isFile; +function isDir(filepath) { + try { + const stats = fs.statSync(filepath); + if (stats.isDirectory()) { + return true; + } + return false; + } + catch (_a) { + return false; + } +} +exports.isDir = isDir; + + /***/ }), /***/ 3555: @@ -82518,6 +82711,34 @@ async function cleanup(ref) { exports.cleanup = cleanup; +/***/ }), + +/***/ 6633: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.installXmake = void 0; +const exec_1 = __nccwpck_require__(7775); +const os = __nccwpck_require__(2037); +const versions_1 = __nccwpck_require__(406); +const win_install_1 = __nccwpck_require__(1612); +const unix_install_1 = __nccwpck_require__(6689); +async function installXmake() { + const version = await (0, versions_1.selectVersion)(); + if (os.platform() === 'win32' || os.platform() === 'cygwin') { + const latest = await (0, versions_1.selectVersion)('latest'); + await (0, win_install_1.winInstall)(version, latest); + } + else { + await (0, unix_install_1.unixInstall)(version); + } + await (0, exec_1.exec)('xmake --root --version'); +} +exports.installXmake = installXmake; + + /***/ }), /***/ 841: @@ -82543,6 +82764,149 @@ function Repo(repo) { exports.Repo = Repo; +/***/ }), + +/***/ 8884: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.savePackageCache = exports.loadPackageCache = void 0; +const core = __nccwpck_require__(9093); +const exec_1 = __nccwpck_require__(7775); +const io = __nccwpck_require__(2826); +const cache = __nccwpck_require__(6878); +const os = __nccwpck_require__(2037); +const path = __nccwpck_require__(1017); +const fsutils = __nccwpck_require__(4295); +function getProjectRootPath() { + let projectRootPath = core.getInput('project-path'); + if (!projectRootPath) { + projectRootPath = process.cwd(); + } + projectRootPath = projectRootPath.trim(); + if (projectRootPath && projectRootPath !== '' && !path.isAbsolute(projectRootPath)) { + projectRootPath = path.join(process.cwd(), projectRootPath); + } + return projectRootPath; +} +async function getPackageCacheKey() { + var _a; + let packageCacheKey = core.getInput('package-cache-key'); + if (!packageCacheKey) { + packageCacheKey = ''; + } + let packageCacheHash = ''; + const projectRootPath = getProjectRootPath(); + if (projectRootPath && projectRootPath !== '' && fsutils.isDir(projectRootPath)) { + const options = {}; + options.cwd = projectRootPath; + options.listeners = { + stdout: (data) => { + packageCacheHash += data.toString(); + }, + }; + await (0, exec_1.exec)('xmake', ['repo', '--update']); + await (0, exec_1.exec)('xmake', ['l', 'utils.ci.packageskey'], options); + packageCacheHash = packageCacheHash.trim(); + } + return `xmake-package-cache-${packageCacheKey}-${packageCacheHash}-${os.arch()}-${os.platform()}-${(_a = process.env.RUNNER_OS) !== null && _a !== void 0 ? _a : 'unknown'}`; +} +async function getPackageCachePath() { + let packageCachePath = ''; + const options = {}; + options.listeners = { + stdout: (data) => { + packageCachePath += data.toString(); + }, + }; + await (0, exec_1.exec)('xmake', ['l', '-c', 'import("core.package.package"); print(package.installdir())'], options); + packageCachePath = packageCachePath.trim(); + return packageCachePath; +} +function getPackageCacheFolder() { + return '.xmake-package-cache'; +} +async function loadPackageCache() { + const packageCache = core.getBooleanInput('package-cache'); + if (!packageCache) { + return; + } + const packageCacheFolder = getPackageCacheFolder(); + const packageCacheKey = await getPackageCacheKey(); + const packageCachePath = await getPackageCachePath(); + if (!packageCachePath || packageCachePath === '') { + return; + } + if (packageCacheFolder && process.env.GITHUB_WORKSPACE) { + const fullCachePath = path.join(process.env.GITHUB_WORKSPACE, packageCacheFolder); + const filepath = path.join(fullCachePath, 'package_cache_saved.txt'); + if (!fsutils.isFile(filepath)) { + core.info(`Restore package cache path: ${fullCachePath} to ${packageCachePath}, key: ${packageCacheKey}`); + await cache.restoreCache([packageCacheFolder], packageCacheKey); + } + if (fsutils.isFile(filepath)) { + if (fsutils.isDir(packageCachePath)) { + await io.rmRF(packageCachePath); + } + await io.cp(fullCachePath, packageCachePath, { + recursive: true, + }); + core.saveState('hitPackageCache', 'true'); + } + else { + core.warning(`No cached files found at path "${fullCachePath}".`); + await io.rmRF(fullCachePath); + } + } +} +exports.loadPackageCache = loadPackageCache; +async function savePackageCache() { + const packageCache = core.getBooleanInput('package-cache'); + if (!packageCache) { + return; + } + const packageCacheFolder = getPackageCacheFolder(); + const packageCacheKey = await getPackageCacheKey(); + const packageCachePath = await getPackageCachePath(); + if (!packageCachePath || packageCachePath === '') { + return; + } + const hitPackageCache = !!core.getState('hitPackageCache'); + if (!hitPackageCache && packageCacheFolder && process.env.GITHUB_WORKSPACE && fsutils.isDir(packageCachePath)) { + const fullCachePath = path.join(process.env.GITHUB_WORKSPACE, packageCacheFolder); + core.info(`Save package cache path: ${packageCachePath} to ${fullCachePath}, key: ${packageCacheKey}`); + await io.cp(packageCachePath, fullCachePath, { + recursive: true, + }); + await cache.saveCache([packageCacheFolder], packageCacheKey); + } +} +exports.savePackageCache = savePackageCache; + + +/***/ }), + +/***/ 8387: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.IsPost = void 0; +const core = __nccwpck_require__(9093); +/** + * Indicates whether the POST action is running + */ +exports.IsPost = !!core.getState('isPost'); +// Publish a variable so that when the POST action runs, it can determine it should run the cleanup logic. +// This is necessary since we don't have a separate entry point. +if (!exports.IsPost) { + core.saveState('isPost', 'true'); +} + + /***/ }), /***/ 6689: @@ -87430,29 +87794,37 @@ var exports = __webpack_exports__; Object.defineProperty(exports, "__esModule", ({ value: true })); const core = __nccwpck_require__(9093); -const exec_1 = __nccwpck_require__(7775); -const os = __nccwpck_require__(2037); -const versions_1 = __nccwpck_require__(406); -const win_install_1 = __nccwpck_require__(1612); -const unix_install_1 = __nccwpck_require__(6689); +const stateHelper = __nccwpck_require__(8387); +const install_1 = __nccwpck_require__(6633); +const build_cache_1 = __nccwpck_require__(53); +const package_cache_1 = __nccwpck_require__(8884); async function run() { try { - const version = await (0, versions_1.selectVersion)(); - if (os.platform() === 'win32' || os.platform() === 'cygwin') { - const latest = await (0, versions_1.selectVersion)('latest'); - await (0, win_install_1.winInstall)(version, latest); - } - else { - await (0, unix_install_1.unixInstall)(version); - } - await (0, exec_1.exec)('xmake --root --version'); + await (0, install_1.installXmake)(); + await (0, build_cache_1.loadBuildCache)(); + await (0, package_cache_1.loadPackageCache)(); } catch (error) { const ex = error; core.setFailed(ex.message); } } -run().catch((e) => core.error(e)); +async function saveCache() { + try { + await (0, build_cache_1.saveBuildCache)(); + await (0, package_cache_1.savePackageCache)(); + } + catch (error) { + const ex = error; + core.setFailed(ex.message); + } +} +if (!stateHelper.IsPost) { + run().catch((e) => core.error(e)); +} +else { + saveCache().catch((e) => core.error(e)); +} })(); diff --git a/src/build-cache.ts b/src/build-cache.ts new file mode 100644 index 0000000..679c8be --- /dev/null +++ b/src/build-cache.ts @@ -0,0 +1,159 @@ +import * as core from '@actions/core'; +import { exec, ExecOptions } from '@actions/exec'; +import * as io from '@actions/io'; +import * as cache from '@actions/cache'; +import * as os from 'os'; +import * as path from 'path'; +import * as fsutils from './fsutils'; + +function getBuildTime(hours?: string): string { + let key = 'BuildTime'; + if (hours && hours !== '') { + key = key + hours; + } + let buildTime = core.getState(key); + if (!buildTime || buildTime === '') { + const now = new Date(); + const year = now.getFullYear(); + const month = String(now.getMonth() + 1).padStart(2, '0'); + const day = String(now.getDate()).padStart(2, '0'); + if (!hours || hours === '') { + hours = String(now.getHours()).padStart(2, '0'); + } + buildTime = `${year}${month}${day}_${hours}`; + core.saveState(key, buildTime); + } + return buildTime; +} + +function getProjectRootPath(): string { + let projectRootPath = core.getInput('project-path'); + if (!projectRootPath) { + projectRootPath = process.cwd(); + } + projectRootPath = projectRootPath.trim(); + if (projectRootPath && projectRootPath !== '' && !path.isAbsolute(projectRootPath)) { + projectRootPath = path.join(process.cwd(), projectRootPath); + } + return projectRootPath; +} + +function getBuildCacheKey(buildCacheTime?: string): string { + let buildCacheKey = core.getInput('build-cache-key'); + if (!buildCacheKey) { + buildCacheKey = ''; + } + if (!buildCacheTime || buildCacheTime === '') { + buildCacheTime = getBuildTime(); + } + return `xmake-build-cache-${buildCacheKey}-${buildCacheTime}-${os.arch()}-${os.platform()}-${process.env.RUNNER_OS ?? 'unknown'}`; +} + +async function getBuildCachePath(): Promise { + let buildCachePath = core.getInput('build-cache-path'); + if (!buildCachePath) { + buildCachePath = ''; + const projectRootPath = getProjectRootPath(); + if (projectRootPath && projectRootPath !== '' && fsutils.isDir(projectRootPath)) { + const options: ExecOptions = {}; + options.cwd = projectRootPath; + options.listeners = { + stdout: (data: Buffer) => { + buildCachePath += data.toString(); + }, + }; + await exec( + 'xmake', + [ + 'l', + '-c', + 'import("core.project.config"); import("private.cache.build_cache"); config.load(); print(build_cache.rootdir())', + ], + options, + ); + buildCachePath = buildCachePath.trim(); + if (buildCachePath !== '' && !path.isAbsolute(buildCachePath)) { + buildCachePath = path.join(projectRootPath, buildCachePath); + } + } else { + buildCachePath = 'build/.build_cache'; + } + } + return buildCachePath; +} + +function getBuildCacheFolder(): string { + return '.xmake-build-cache'; +} + +export async function loadBuildCache(): Promise { + const buildCache = core.getBooleanInput('build-cache'); + if (!buildCache) { + return; + } + + // export $XMAKE_ACTION_BUILD_CACHE, xmake will check it and enable build cache by default on ci. + core.exportVariable('XMAKE_ACTION_BUILD_CACHE', 'true'); + + const buildCacheFolder = getBuildCacheFolder(); + const buildCachePath = await getBuildCachePath(); + + if (buildCacheFolder && process.env.GITHUB_WORKSPACE) { + const fullCachePath = path.join(process.env.GITHUB_WORKSPACE, buildCacheFolder); + const filepath = path.join(fullCachePath, 'build_cache_saved.txt'); + // Since action/cache cannot overwrite updates, we try to restore the cache from the last 24 hours. + const now = new Date(); + for (let i = 0; i < 24; i++) { + const hours = now.getHours() - i; + if (hours < 0) { + break; + } + const buildCacheKey = getBuildCacheKey(getBuildTime(String(hours).padStart(2, '0'))); + if (!fsutils.isFile(filepath)) { + core.info(`Restore build cache path: ${fullCachePath} to ${buildCachePath}, key: ${buildCacheKey}`); + await cache.restoreCache([buildCacheFolder], buildCacheKey); + } + if (fsutils.isFile(filepath)) { + if (fsutils.isDir(buildCachePath)) { + await io.rmRF(buildCachePath); + } + await io.cp(fullCachePath, buildCachePath, { + recursive: true, + }); + /* Even if the historical cache is hit, we need to update to the latest cache + * Therefore, only when the latest cache is hit, it is a real hit, and we no longer need to update the cache. + */ + if (i === 0) { + core.saveState('hitBuildCache', 'true'); + } + break; + } + } + if (!fsutils.isFile(filepath)) { + core.warning(`No cached files found at path "${fullCachePath}".`); + await io.rmRF(fullCachePath); + } + } +} + +export async function saveBuildCache(): Promise { + const buildCache = core.getBooleanInput('build-cache'); + if (!buildCache) { + return; + } + + const buildCacheFolder = getBuildCacheFolder(); + const buildCacheKey = getBuildCacheKey(); + const buildCachePath = await getBuildCachePath(); + + const hitBuildCache = !!core.getState('hitBuildCache'); + if (!hitBuildCache && buildCacheFolder && process.env.GITHUB_WORKSPACE && fsutils.isDir(buildCachePath)) { + const fullCachePath = path.join(process.env.GITHUB_WORKSPACE, buildCacheFolder); + core.info(`Save build cache path: ${buildCachePath} to ${fullCachePath}, key: ${buildCacheKey}`); + await io.cp(buildCachePath, fullCachePath, { + recursive: true, + }); + await exec('xmake', ['l', 'os.touch', path.join(fullCachePath, 'build_cache_saved.txt')]); + await cache.saveCache([buildCacheFolder], buildCacheKey); + } +} diff --git a/src/fsutils.ts b/src/fsutils.ts new file mode 100644 index 0000000..7720574 --- /dev/null +++ b/src/fsutils.ts @@ -0,0 +1,22 @@ +import * as fs from 'fs'; + +export function isFile(filepath: string): boolean { + try { + fs.accessSync(filepath, fs.constants.F_OK); + return true; + } catch { + return false; + } +} + +export function isDir(filepath: string): boolean { + try { + const stats = fs.statSync(filepath); + if (stats.isDirectory()) { + return true; + } + return false; + } catch { + return false; + } +} diff --git a/src/index.ts b/src/index.ts index c47ea5a..7459cfe 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,24 +1,32 @@ import * as core from '@actions/core'; -import { exec } from '@actions/exec'; -import * as os from 'os'; -import { selectVersion } from './versions'; -import { winInstall } from './win-install'; -import { unixInstall } from './unix-install'; +import * as stateHelper from './state-helper'; +import { installXmake } from './install'; +import { loadBuildCache, saveBuildCache } from './build-cache'; +import { loadPackageCache, savePackageCache } from './package-cache'; async function run(): Promise { try { - const version = await selectVersion(); - if (os.platform() === 'win32' || os.platform() === 'cygwin') { - const latest = await selectVersion('latest'); - await winInstall(version, latest); - } else { - await unixInstall(version); - } - await exec('xmake --root --version'); + await installXmake(); + await loadBuildCache(); + await loadPackageCache(); } catch (error) { const ex = error as Error; core.setFailed(ex.message); } } -run().catch((e: Error) => core.error(e)); +async function saveCache(): Promise { + try { + await saveBuildCache(); + await savePackageCache(); + } catch (error) { + const ex = error as Error; + core.setFailed(ex.message); + } +} + +if (!stateHelper.IsPost) { + run().catch((e: Error) => core.error(e)); +} else { + saveCache().catch((e: Error) => core.error(e)); +} diff --git a/src/install.ts b/src/install.ts new file mode 100644 index 0000000..7525a4e --- /dev/null +++ b/src/install.ts @@ -0,0 +1,16 @@ +import { exec } from '@actions/exec'; +import * as os from 'os'; +import { selectVersion } from './versions'; +import { winInstall } from './win-install'; +import { unixInstall } from './unix-install'; + +export async function installXmake(): Promise { + const version = await selectVersion(); + if (os.platform() === 'win32' || os.platform() === 'cygwin') { + const latest = await selectVersion('latest'); + await winInstall(version, latest); + } else { + await unixInstall(version); + } + await exec('xmake --root --version'); +} diff --git a/src/package-cache.ts b/src/package-cache.ts new file mode 100644 index 0000000..819fcec --- /dev/null +++ b/src/package-cache.ts @@ -0,0 +1,117 @@ +import * as core from '@actions/core'; +import { exec, ExecOptions } from '@actions/exec'; +import * as io from '@actions/io'; +import * as cache from '@actions/cache'; +import * as os from 'os'; +import * as path from 'path'; +import * as fsutils from './fsutils'; + +function getProjectRootPath(): string { + let projectRootPath = core.getInput('project-path'); + if (!projectRootPath) { + projectRootPath = process.cwd(); + } + projectRootPath = projectRootPath.trim(); + if (projectRootPath && projectRootPath !== '' && !path.isAbsolute(projectRootPath)) { + projectRootPath = path.join(process.cwd(), projectRootPath); + } + return projectRootPath; +} + +async function getPackageCacheKey(): Promise { + let packageCacheKey = core.getInput('package-cache-key'); + if (!packageCacheKey) { + packageCacheKey = ''; + } + let packageCacheHash = ''; + const projectRootPath = getProjectRootPath(); + if (projectRootPath && projectRootPath !== '' && fsutils.isDir(projectRootPath)) { + const options: ExecOptions = {}; + options.cwd = projectRootPath; + options.listeners = { + stdout: (data: Buffer) => { + packageCacheHash += data.toString(); + }, + }; + await exec('xmake', ['repo', '--update']); + await exec('xmake', ['l', 'utils.ci.packageskey'], options); + packageCacheHash = packageCacheHash.trim(); + } + return `xmake-package-cache-${packageCacheKey}-${packageCacheHash}-${os.arch()}-${os.platform()}-${process.env.RUNNER_OS ?? 'unknown'}`; +} + +async function getPackageCachePath(): Promise { + let packageCachePath = ''; + const options: ExecOptions = {}; + options.listeners = { + stdout: (data: Buffer) => { + packageCachePath += data.toString(); + }, + }; + await exec('xmake', ['l', '-c', 'import("core.package.package"); print(package.installdir())'], options); + packageCachePath = packageCachePath.trim(); + return packageCachePath; +} + +function getPackageCacheFolder(): string { + return '.xmake-package-cache'; +} + +export async function loadPackageCache(): Promise { + const packageCache = core.getBooleanInput('package-cache'); + if (!packageCache) { + return; + } + + const packageCacheFolder = getPackageCacheFolder(); + const packageCacheKey = await getPackageCacheKey(); + const packageCachePath = await getPackageCachePath(); + if (!packageCachePath || packageCachePath === '') { + return; + } + + if (packageCacheFolder && process.env.GITHUB_WORKSPACE) { + const fullCachePath = path.join(process.env.GITHUB_WORKSPACE, packageCacheFolder); + const filepath = path.join(fullCachePath, 'package_cache_saved.txt'); + if (!fsutils.isFile(filepath)) { + core.info(`Restore package cache path: ${fullCachePath} to ${packageCachePath}, key: ${packageCacheKey}`); + await cache.restoreCache([packageCacheFolder], packageCacheKey); + } + if (fsutils.isFile(filepath)) { + if (fsutils.isDir(packageCachePath)) { + await io.rmRF(packageCachePath); + } + await io.cp(fullCachePath, packageCachePath, { + recursive: true, + }); + core.saveState('hitPackageCache', 'true'); + } else { + core.warning(`No cached files found at path "${fullCachePath}".`); + await io.rmRF(fullCachePath); + } + } +} + +export async function savePackageCache(): Promise { + const packageCache = core.getBooleanInput('package-cache'); + if (!packageCache) { + return; + } + + const packageCacheFolder = getPackageCacheFolder(); + const packageCacheKey = await getPackageCacheKey(); + const packageCachePath = await getPackageCachePath(); + if (!packageCachePath || packageCachePath === '') { + return; + } + + const hitPackageCache = !!core.getState('hitPackageCache'); + if (!hitPackageCache && packageCacheFolder && process.env.GITHUB_WORKSPACE && fsutils.isDir(packageCachePath)) { + const fullCachePath = path.join(process.env.GITHUB_WORKSPACE, packageCacheFolder); + core.info(`Save package cache path: ${packageCachePath} to ${fullCachePath}, key: ${packageCacheKey}`); + await io.cp(packageCachePath, fullCachePath, { + recursive: true, + }); + await cache.saveCache([packageCacheFolder], packageCacheKey); + } +} diff --git a/src/state-helper.ts b/src/state-helper.ts new file mode 100644 index 0000000..c78ab86 --- /dev/null +++ b/src/state-helper.ts @@ -0,0 +1,12 @@ +import * as core from '@actions/core'; + +/** + * Indicates whether the POST action is running + */ +export const IsPost = !!core.getState('isPost'); + +// Publish a variable so that when the POST action runs, it can determine it should run the cleanup logic. +// This is necessary since we don't have a separate entry point. +if (!IsPost) { + core.saveState('isPost', 'true'); +} diff --git a/tests/sample/.gitignore b/tests/sample/.gitignore new file mode 100644 index 0000000..1521057 --- /dev/null +++ b/tests/sample/.gitignore @@ -0,0 +1,8 @@ +# Xmake cache +.xmake/ +build/ + +# MacOS Cache +.DS_Store + + diff --git a/tests/sample/src/main.cpp b/tests/sample/src/main.cpp new file mode 100644 index 0000000..7c775d2 --- /dev/null +++ b/tests/sample/src/main.cpp @@ -0,0 +1,6 @@ +#include + +int main(int argc, char** argv) { + std::cout << "hello world!" << std::endl; + return 0; +} diff --git a/tests/sample/xmake.lua b/tests/sample/xmake.lua new file mode 100644 index 0000000..8c88caf --- /dev/null +++ b/tests/sample/xmake.lua @@ -0,0 +1,9 @@ +add_rules("mode.debug", "mode.release") + +add_requires("libpng", "libogg", {system = false}) + +target("sample") + set_kind("binary") + add_files("src/*.cpp") + add_packages("libpng", "libogg") +