From 07b0befcc8903d02b0bd5f519237ac93f30c5435 Mon Sep 17 00:00:00 2001 From: Christophe Bedard Date: Tue, 9 Mar 2021 20:03:04 -0500 Subject: [PATCH] Add colcon-defaults input (#570) Relates to #525, #526, #555 This adds a `colcon-defaults` input. The JSON content is validated and then written to `COLCON_DEFAULTS_FILE` Signed-off-by: Christophe Bedard --- .github/workflows/test.yml | 16 ++++++++++++ README.md | 24 ++++++++++++++++++ action.yml | 7 ++++++ dist/index.js | 46 ++++++++++++++++++++++++++-------- src/action-ros-ci.ts | 51 +++++++++++++++++++++++++++++++++----- 5 files changed, 127 insertions(+), 17 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fb62a0600..60e1889e7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -240,6 +240,22 @@ jobs: target-ros2-distro: ${{ matrix.ros_distribution }} - run: test -d "${{ steps.test_repo.outputs.ros-workspace-directory-name }}/install/ament_copyright" + - uses: ./ + id: test_colcon_defaults + name: "Test single package, with colcon defaults" + with: + colcon-defaults: | + { + "build": { + "build-base": "build-custom" + } + } + package-name: ament_copyright + target-ros2-distro: ${{ matrix.ros_distribution }} + vcs-repo-file-url: ${{ env.DISTRO_REPOS_URL }} + - run: test -d "${{ steps.test_colcon_defaults.outputs.ros-workspace-directory-name }}/install/ament_copyright" + - run: test -d "${{ steps.test_colcon_defaults.outputs.ros-workspace-directory-name }}/build-custom" + # The second repo file is ignored, but will get vcs-import'ed anyway. # This test case just run basic testing on the action logic, making # sure the for-loop is implemented correctly. diff --git a/README.md b/README.md index 6e73f6c44..e92018c1b 100644 --- a/README.md +++ b/README.md @@ -104,6 +104,30 @@ steps: target-ros1-distro: melodic ``` +### Use a `colcon` `defaults.yaml` file + +To use a [`colcon` `defaults.yaml` file](https://colcon.readthedocs.io/en/released/user/configuration.html#defaults-yaml), provide a valid JSON string through the `colcon-defaults` input. +This allows using a `colcon` option/argument that is not exposed by this action's inputs. + +```yaml +steps: + - uses: ros-tooling/setup-ros@v0.1 + with: + required-ros-distributions: foxy + - uses: ros-tooling/action-ros-ci@v0.1 + with: + package-name: my_package + target-ros2-distro: foxy + colcon-defaults: | + { + "build": { + "cmake-args": [ + "-DMY_CUSTOM_OPTION=ON" + ] + } + } +``` + ### Enable Address Sanitizer to automatically report memory issues [ASan][addresssanitizer] is an open-source tool developed to automatically report diff --git a/action.yml b/action.yml index 4c9404207..71f480cd0 100644 --- a/action.yml +++ b/action.yml @@ -5,6 +5,13 @@ branding: icon: "activity" color: "gray-dark" inputs: + colcon-defaults: + default: "" + description: | + Valid JSON content to use as a colcon defaults.yaml file. + Use a pipe to provide a multiline string. + See: https://colcon.readthedocs.io/en/released/user/configuration.html#defaults-yaml + required: false colcon-mixin-name: default: "" description: "Colcon mixin to be used to compile and test (empty means no mixin)" diff --git a/dist/index.js b/dist/index.js index 65b9599be..3fe6349a9 100644 --- a/dist/index.js +++ b/dist/index.js @@ -10874,6 +10874,21 @@ const targetROS1DistroInput = "target-ros1-distro"; const targetROS2DistroInput = "target-ros2-distro"; const isLinux = process.platform == "linux"; const isWindows = process.platform == "win32"; +/** + * Check if a string is a valid JSON string. + * + * @param str the string to validate + * @returns `true` if valid, `false` otherwise + */ +function isValidJson(str) { + try { + JSON.parse(str); + } + catch (e) { + return false; + } + return true; +} /** * Convert local paths to URLs. * @@ -10993,6 +11008,7 @@ function run() { try { const repo = github.context.repo; const workspace = process.env.GITHUB_WORKSPACE; + const colconDefaults = core.getInput("colcon-defaults"); const colconMixinName = core.getInput("colcon-mixin-name"); const colconMixinRepo = core.getInput("colcon-mixin-repository"); const extraCmakeArgs = core.getInput("extra-cmake-args"); @@ -11045,8 +11061,17 @@ function run() { retries: 3, }); } - // Reset colcon configuration. + // Reset colcon configuration and create defaults file if one was provided. yield io.rmRF(path.join(os.homedir(), ".colcon")); + let colconDefaultsFile = ""; + if (colconDefaults.length > 0) { + if (!isValidJson(colconDefaults)) { + core.setFailed(`colcon-defaults value is not a valid JSON string:\n${colconDefaults}`); + return; + } + colconDefaultsFile = path.join(fs_1.default.mkdtempSync(path.join(os.tmpdir(), "colcon-defaults-")), "defaults.yaml"); + fs_1.default.writeFileSync(colconDefaultsFile, colconDefaults); + } // Wipe out the workspace directory to ensure the workspace is always // identical. yield io.rmRF(rosWorkspaceDir); @@ -11063,6 +11088,11 @@ function run() { const options = { cwd: rosWorkspaceDir, }; + if (colconDefaultsFile !== "") { + options.env = { + COLCON_DEFAULTS_FILE: colconDefaultsFile, + }; + } const curlFlags = curlFlagsArray.join(" "); for (const vcsRepoFileUrl of vcsRepoFileUrlListResolved) { yield execBashCommand(`curl ${curlFlags} '${vcsRepoFileUrl}' | vcs import --force --recursive src/`, undefined, options); @@ -11098,8 +11128,8 @@ function run() { yield execBashCommand("vcs log -l1 src/", undefined, options); yield installRosdeps(packageNames, rosWorkspaceDir, targetRos1Distro, targetRos2Distro); if (colconMixinName !== "" && colconMixinRepo !== "") { - yield execBashCommand(`colcon mixin add default '${colconMixinRepo}'`); - yield execBashCommand("colcon mixin update default"); + yield execBashCommand(`colcon mixin add default '${colconMixinRepo}'`, undefined, options); + yield execBashCommand("colcon mixin update default", undefined, options); } let extra_options = []; if (colconMixinName !== "") { @@ -11161,10 +11191,7 @@ function run() { // ignoreReturnCode is set to true to avoid having a lack of coverage // data fail the build. const colconLcovInitialCmd = "colcon lcov-result --initial"; - yield execBashCommand(colconLcovInitialCmd, colconCommandPrefix, { - cwd: rosWorkspaceDir, - ignoreReturnCode: true, - }); + yield execBashCommand(colconLcovInitialCmd, colconCommandPrefix, Object.assign(Object.assign({}, options), { ignoreReturnCode: true })); const colconTestCmd = [ `colcon test`, `--event-handlers console_cohesion+`, @@ -11180,10 +11207,7 @@ function run() { `--filter ${coverageIgnorePattern}`, `--packages-select ${packageNames}`, ].join(" "); - yield execBashCommand(colconLcovResultCmd, colconCommandPrefix, { - cwd: rosWorkspaceDir, - ignoreReturnCode: true, - }); + yield execBashCommand(colconLcovResultCmd, colconCommandPrefix, Object.assign(Object.assign({}, options), { ignoreReturnCode: true })); const colconCoveragepyResultCmd = [ `colcon coveragepy-result`, `--packages-select ${packageNames}`, diff --git a/src/action-ros-ci.ts b/src/action-ros-ci.ts index 0f0749820..9543483ac 100644 --- a/src/action-ros-ci.ts +++ b/src/action-ros-ci.ts @@ -47,6 +47,21 @@ const targetROS2DistroInput: string = "target-ros2-distro"; const isLinux: boolean = process.platform == "linux"; const isWindows: boolean = process.platform == "win32"; +/** + * Check if a string is a valid JSON string. + * + * @param str the string to validate + * @returns `true` if valid, `false` otherwise + */ +function isValidJson(str: string): boolean { + try { + JSON.parse(str); + } catch (e) { + return false; + } + return true; +} + /** * Convert local paths to URLs. * @@ -195,6 +210,7 @@ async function run() { const repo = github.context.repo; const workspace = process.env.GITHUB_WORKSPACE as string; + const colconDefaults = core.getInput("colcon-defaults"); const colconMixinName = core.getInput("colcon-mixin-name"); const colconMixinRepo = core.getInput("colcon-mixin-repository"); const extraCmakeArgs = core.getInput("extra-cmake-args"); @@ -268,8 +284,22 @@ async function run() { ); } - // Reset colcon configuration. + // Reset colcon configuration and create defaults file if one was provided. await io.rmRF(path.join(os.homedir(), ".colcon")); + let colconDefaultsFile = ""; + if (colconDefaults.length > 0) { + if (!isValidJson(colconDefaults)) { + core.setFailed( + `colcon-defaults value is not a valid JSON string:\n${colconDefaults}` + ); + return; + } + colconDefaultsFile = path.join( + fs.mkdtempSync(path.join(os.tmpdir(), "colcon-defaults-")), + "defaults.yaml" + ); + fs.writeFileSync(colconDefaultsFile, colconDefaults); + } // Wipe out the workspace directory to ensure the workspace is always // identical. @@ -287,9 +317,14 @@ async function run() { fs.appendFileSync(path.join(os.homedir(), ".gitconfig"), config); } - const options = { + const options: im.ExecOptions = { cwd: rosWorkspaceDir, }; + if (colconDefaultsFile !== "") { + options.env = { + COLCON_DEFAULTS_FILE: colconDefaultsFile, + }; + } const curlFlags = curlFlagsArray.join(" "); for (const vcsRepoFileUrl of vcsRepoFileUrlListResolved) { @@ -346,8 +381,12 @@ async function run() { ); if (colconMixinName !== "" && colconMixinRepo !== "") { - await execBashCommand(`colcon mixin add default '${colconMixinRepo}'`); - await execBashCommand("colcon mixin update default"); + await execBashCommand( + `colcon mixin add default '${colconMixinRepo}'`, + undefined, + options + ); + await execBashCommand("colcon mixin update default", undefined, options); } let extra_options: string[] = []; @@ -414,7 +453,7 @@ async function run() { // data fail the build. const colconLcovInitialCmd = "colcon lcov-result --initial"; await execBashCommand(colconLcovInitialCmd, colconCommandPrefix, { - cwd: rosWorkspaceDir, + ...options, ignoreReturnCode: true, }); @@ -435,7 +474,7 @@ async function run() { `--packages-select ${packageNames}`, ].join(" "); await execBashCommand(colconLcovResultCmd, colconCommandPrefix, { - cwd: rosWorkspaceDir, + ...options, ignoreReturnCode: true, });