diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 28b3bcc0..775376a3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,9 +22,12 @@ jobs: with: xmake-version: ${{ matrix.version }} actions-cache-folder: '.xmake-cache' + actions-cache-key: ${{ matrix.version }} build-cache: true - build-cache-path: 'tests/sample/build/.build_cache' + build-cache-key: ${{ matrix.version }} package-cache: true + package-cache-key: ${{ matrix.version }} + project-path: 'tests/sample' - name: Run tests run: | diff --git a/README.md b/README.md index 37187c0c..1dda5eb0 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,7 @@ 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 @@ -90,6 +91,16 @@ with: 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 diff --git a/action.yml b/action.yml index 90b835e7..4e563527 100644 --- a/action.yml +++ b/action.yml @@ -22,6 +22,9 @@ inputs: 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 diff --git a/dist/index.js b/dist/index.js index 7f3971d7..1ddc108f 100644 --- a/dist/index.js +++ b/dist/index.js @@ -82464,18 +82464,73 @@ const cache = __nccwpck_require__(6878); const os = __nccwpck_require__(2037); const path = __nccwpck_require__(1017); const fsutils = __nccwpck_require__(4295); -function getBuildCacheKey() { +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 = ''; } - return `xmake-build-cache-${buildCacheKey}-${os.arch()}-${os.platform()}-${(_a = process.env.RUNNER_OS) !== null && _a !== void 0 ? _a : 'unknown'}`; + 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'}`; } -function getBuildCachePath() { +async function getBuildCachePath() { let buildCachePath = core.getInput('build-cache-path'); if (!buildCachePath) { - buildCachePath = 'build/.build_cache'; + 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; } @@ -82490,21 +82545,39 @@ async function loadBuildCache() { // 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 buildCacheKey = getBuildCacheKey(); - const buildCachePath = getBuildCachePath(); + 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'); - if (!fsutils.isFile(filepath)) { - core.info(`Restore build cache path: ${fullCachePath} to ${buildCachePath}, key: ${buildCacheKey}`); - await cache.restoreCache([buildCacheFolder], buildCacheKey); - } - if (fsutils.isFile(filepath)) { - await io.cp(fullCachePath, buildCachePath, { - recursive: true, - }); + // 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; + } } - else { + if (!fsutils.isFile(filepath)) { core.warning(`No cached files found at path "${fullCachePath}".`); await io.rmRF(fullCachePath); } @@ -82518,8 +82591,9 @@ async function saveBuildCache() { } const buildCacheFolder = getBuildCacheFolder(); const buildCacheKey = getBuildCacheKey(); - const buildCachePath = getBuildCachePath(); - if (buildCacheFolder && process.env.GITHUB_WORKSPACE && fsutils.isDir(buildCachePath)) { + 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, { @@ -82706,13 +82780,38 @@ const cache = __nccwpck_require__(6878); const os = __nccwpck_require__(2037); const path = __nccwpck_require__(1017); const fsutils = __nccwpck_require__(4295); -function getPackageCacheKey() { +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 = ''; } - return `xmake-package-cache-${packageCacheKey}-${os.arch()}-${os.platform()}-${(_a = process.env.RUNNER_OS) !== null && _a !== void 0 ? _a : 'unknown'}`; + 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 = ''; @@ -82724,7 +82823,6 @@ async function getPackageCachePath() { }; await (0, exec_1.exec)('xmake', ['l', '-c', 'import("core.package.package"); print(package.installdir())'], options); packageCachePath = packageCachePath.trim(); - core.info(`packageCachePath: ${packageCachePath}`); return packageCachePath; } function getPackageCacheFolder() { @@ -82736,7 +82834,7 @@ async function loadPackageCache() { return; } const packageCacheFolder = getPackageCacheFolder(); - const packageCacheKey = getPackageCacheKey(); + const packageCacheKey = await getPackageCacheKey(); const packageCachePath = await getPackageCachePath(); if (!packageCachePath || packageCachePath === '') { return; @@ -82749,9 +82847,13 @@ async function loadPackageCache() { 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}".`); @@ -82766,18 +82868,18 @@ async function savePackageCache() { return; } const packageCacheFolder = getPackageCacheFolder(); - const packageCacheKey = getPackageCacheKey(); + const packageCacheKey = await getPackageCacheKey(); const packageCachePath = await getPackageCachePath(); if (!packageCachePath || packageCachePath === '') { return; } - if (packageCacheFolder && process.env.GITHUB_WORKSPACE && fsutils.isDir(packageCachePath)) { + 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 (0, exec_1.exec)('xmake', ['l', 'os.touch', path.join(fullCachePath, 'package_cache_saved.txt')]); await cache.saveCache([packageCacheFolder], packageCacheKey); } } @@ -82792,28 +82894,12 @@ exports.savePackageCache = savePackageCache; "use strict"; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.SshKnownHostsPath = exports.SshKeyPath = exports.PostSetSafeDirectory = exports.RepositoryPath = exports.IsPost = void 0; +exports.IsPost = void 0; const core = __nccwpck_require__(9093); /** * Indicates whether the POST action is running */ exports.IsPost = !!core.getState('isPost'); -/** - * The repository path for the POST action. The value is empty during the MAIN action. - */ -exports.RepositoryPath = core.getState('repositoryPath'); -/** - * The set-safe-directory for the POST action. The value is set if input: 'safe-directory' is set during the MAIN action. - */ -exports.PostSetSafeDirectory = core.getState('setSafeDirectory') === 'true'; -/** - * The SSH key path for the POST action. The value is empty during the MAIN action. - */ -exports.SshKeyPath = core.getState('sshKeyPath'); -/** - * The SSH known hosts path for the POST action. The value is empty during the MAIN action. - */ -exports.SshKnownHostsPath = core.getState('sshKnownHostsPath'); // 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) { diff --git a/src/build-cache.ts b/src/build-cache.ts index 4d91471d..679c8be6 100644 --- a/src/build-cache.ts +++ b/src/build-cache.ts @@ -1,23 +1,83 @@ import * as core from '@actions/core'; -import { exec } from '@actions/exec'; +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 getBuildCacheKey(): string { +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 = ''; } - return `xmake-build-cache-${buildCacheKey}-${os.arch()}-${os.platform()}-${process.env.RUNNER_OS ?? 'unknown'}`; + if (!buildCacheTime || buildCacheTime === '') { + buildCacheTime = getBuildTime(); + } + return `xmake-build-cache-${buildCacheKey}-${buildCacheTime}-${os.arch()}-${os.platform()}-${process.env.RUNNER_OS ?? 'unknown'}`; } -function getBuildCachePath(): string { +async function getBuildCachePath(): Promise { let buildCachePath = core.getInput('build-cache-path'); if (!buildCachePath) { - buildCachePath = 'build/.build_cache'; + 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; } @@ -36,21 +96,40 @@ export async function loadBuildCache(): Promise { core.exportVariable('XMAKE_ACTION_BUILD_CACHE', 'true'); const buildCacheFolder = getBuildCacheFolder(); - const buildCacheKey = getBuildCacheKey(); - const buildCachePath = getBuildCachePath(); + 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'); - if (!fsutils.isFile(filepath)) { - core.info(`Restore build cache path: ${fullCachePath} to ${buildCachePath}, key: ${buildCacheKey}`); - await cache.restoreCache([buildCacheFolder], buildCacheKey); + // 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)) { - await io.cp(fullCachePath, buildCachePath, { - recursive: true, - }); - } else { + if (!fsutils.isFile(filepath)) { core.warning(`No cached files found at path "${fullCachePath}".`); await io.rmRF(fullCachePath); } @@ -65,9 +144,10 @@ export async function saveBuildCache(): Promise { const buildCacheFolder = getBuildCacheFolder(); const buildCacheKey = getBuildCacheKey(); - const buildCachePath = getBuildCachePath(); + const buildCachePath = await getBuildCachePath(); - if (buildCacheFolder && process.env.GITHUB_WORKSPACE && fsutils.isDir(buildCachePath)) { + 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, { diff --git a/src/package-cache.ts b/src/package-cache.ts index 0bd36aa9..819fcecf 100644 --- a/src/package-cache.ts +++ b/src/package-cache.ts @@ -6,12 +6,38 @@ import * as os from 'os'; import * as path from 'path'; import * as fsutils from './fsutils'; -function getPackageCacheKey(): string { +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 = ''; } - return `xmake-package-cache-${packageCacheKey}-${os.arch()}-${os.platform()}-${process.env.RUNNER_OS ?? 'unknown'}`; + 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 { @@ -24,7 +50,6 @@ async function getPackageCachePath(): Promise { }; await exec('xmake', ['l', '-c', 'import("core.package.package"); print(package.installdir())'], options); packageCachePath = packageCachePath.trim(); - core.info(`packageCachePath: ${packageCachePath}`); return packageCachePath; } @@ -39,7 +64,7 @@ export async function loadPackageCache(): Promise { } const packageCacheFolder = getPackageCacheFolder(); - const packageCacheKey = getPackageCacheKey(); + const packageCacheKey = await getPackageCacheKey(); const packageCachePath = await getPackageCachePath(); if (!packageCachePath || packageCachePath === '') { return; @@ -53,9 +78,13 @@ export async function loadPackageCache(): Promise { 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); @@ -70,19 +99,19 @@ export async function savePackageCache(): Promise { } const packageCacheFolder = getPackageCacheFolder(); - const packageCacheKey = getPackageCacheKey(); + const packageCacheKey = await getPackageCacheKey(); const packageCachePath = await getPackageCachePath(); if (!packageCachePath || packageCachePath === '') { return; } - if (packageCacheFolder && process.env.GITHUB_WORKSPACE && fsutils.isDir(packageCachePath)) { + 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 exec('xmake', ['l', 'os.touch', path.join(fullCachePath, 'package_cache_saved.txt')]); await cache.saveCache([packageCacheFolder], packageCacheKey); } } diff --git a/src/state-helper.ts b/src/state-helper.ts index e7045b1c..c78ab861 100644 --- a/src/state-helper.ts +++ b/src/state-helper.ts @@ -5,26 +5,6 @@ import * as core from '@actions/core'; */ export const IsPost = !!core.getState('isPost'); -/** - * The repository path for the POST action. The value is empty during the MAIN action. - */ -export const RepositoryPath = core.getState('repositoryPath'); - -/** - * The set-safe-directory for the POST action. The value is set if input: 'safe-directory' is set during the MAIN action. - */ -export const PostSetSafeDirectory = core.getState('setSafeDirectory') === 'true'; - -/** - * The SSH key path for the POST action. The value is empty during the MAIN action. - */ -export const SshKeyPath = core.getState('sshKeyPath'); - -/** - * The SSH known hosts path for the POST action. The value is empty during the MAIN action. - */ -export const SshKnownHostsPath = core.getState('sshKnownHostsPath'); - // 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) { diff --git a/tests/sample/xmake.lua b/tests/sample/xmake.lua index 8874ed32..8c88caf1 100644 --- a/tests/sample/xmake.lua +++ b/tests/sample/xmake.lua @@ -1,9 +1,9 @@ add_rules("mode.debug", "mode.release") -add_requires("libpng", {system = false}) +add_requires("libpng", "libogg", {system = false}) target("sample") set_kind("binary") add_files("src/*.cpp") - add_packages("libpng") + add_packages("libpng", "libogg")