The version of the "Licensed" tool for use in the GitHub Actions workflow is defined via the "licensee/setup-licensed"
action's `version` input.
Previously the action was configured to install version 3.x of the action. That version is significantly outdated. The
workflow is hereby updated to use the latest version of Licensed.
The "Licensed" tool is used to check the project's compatibility with the licensing of its dependencies.
This tool is installed by the dependency license check GitHub Actions workflow using the `licensee/setup-licensed`
GitHub Actions action. This action attempts the installation according to the following procedure:
1. Install the Ruby gem.
2. If gem installation fails, install the pre-built executable from the GitHub release asset in the `licensee/licensed`
repo.
Previously, the first of these installation methods was failing:
```text
/usr/bin/gem install licensed -v 3.9.1
ERROR: While executing gem ... (Gem::FilePermissionError)
You don't have write permissions for the /var/lib/gems/3.2.0 directory.
/usr/lib/ruby/vendor_ruby/rubygems/installer.rb:713:in `verify_gem_home'
/usr/lib/ruby/vendor_ruby/rubygems/installer.rb:903:in `pre_install_checks'
/usr/lib/ruby/vendor_ruby/rubygems/installer.rb:303:in `install'
/usr/lib/ruby/vendor_ruby/rubygems/resolver/specification.rb:105:in `install'
/usr/lib/ruby/vendor_ruby/rubygems/request_set.rb:195:in `block in install'
/usr/lib/ruby/vendor_ruby/rubygems/request_set.rb:183:in `each'
/usr/lib/ruby/vendor_ruby/rubygems/request_set.rb:183:in `install'
/usr/lib/ruby/vendor_ruby/rubygems/commands/install_command.rb:215:in `install_gem'
/usr/lib/ruby/vendor_ruby/rubygems/commands/install_command.rb:231:in `block in install_gems'
/usr/lib/ruby/vendor_ruby/rubygems/commands/install_command.rb:224:in `each'
/usr/lib/ruby/vendor_ruby/rubygems/commands/install_command.rb:224:in `install_gems'
/usr/lib/ruby/vendor_ruby/rubygems/commands/install_command.rb:170:in `execute'
/usr/lib/ruby/vendor_ruby/rubygems/command.rb:328:in `invoke_with_build_args'
/usr/lib/ruby/vendor_ruby/rubygems/command_manager.rb:253:in `invoke_command'
/usr/lib/ruby/vendor_ruby/rubygems/command_manager.rb:193:in `process_args'
/usr/lib/ruby/vendor_ruby/rubygems/command_manager.rb:151:in `run'
/usr/lib/ruby/vendor_ruby/rubygems/gem_runner.rb:52:in `run'
/usr/bin/gem:12:in `<main>'
gem installation was not successful
```
So it falls back on the second installation method. That method works fine when using a version of "Licensed" <4.0.0.
However, starting from version 4.0.0, pre-built executable are no longer provided, so the only available installation
method for modern versions of "Licensed" is via the Ruby gem.
The gem failure can be avoided by setting up an accessible installation of Ruby in the runner machine, which is
accomplished using the "ruby/setup-ruby" action in a step preceding the `licensee/setup-licensed` step in the workflow.
This GitHub Actions action is used by the dependency license check workflow to install the "Licensed" tool in the
runner workspace.
The action has a convoluted history of ownership: the repository was originally owned by GitHub user "jonabc". It was
later transferred to the "github" organization. Then GitHub abandoned the project, archiving the repository. The
"licensee" organization has now created a hard fork of the action, which is recommended in the readme of the
"github/setup-licensed" repository.
The `licensee` organization has also taken over the management of the "Licensed" tool, and their `licensee` Ruby gem is
a significant dependency of "Licensed". So they will be best equipped to maintain the action going forward.
The workflow is hereby updated to use the now canonical "licensee/setup-licensed" action.
The "licensee/setup-licensed" action maintainers have not provided a major version ref, so it is necessary to pin the
action to the latest release tag.
The "Sync Labels" GitHub Actions workflow is configured to allow the use of multiple shared label configuration files.
This is done by using a job matrix in the GitHub Actions workflow to download each of the files from the source
repository in a parallel GitHub Actions workflow job. A GitHub Actions workflow artifact was used to transfer the
generated files between sequential jobs in the workflow. The "actions/upload-artifact" and "actions/download-artifact"
actions are used for this purpose.
Previously, a single artifact was used for the transfer of all the shared label configuration files, with each of the
parallel jobs uploading its own generated files to that artifact. However, support for uploading multiple times to a
single artifact was dropped in version 4.0.0 of the "actions/upload-artifact" action. So it is now necessary to use a
dedicated artifact for each of the builds. These can be downloaded in aggregate by using the artifact name globbing and
merging features which were introduced in version 4.1.0 of the "actions/download-artifact" action.
A breaking change was made in the 3.2.1 release of the "actions/upload-artifact" action, without doing a major version
bump as would be done in a responsibly maintained project. The action now defaults to not uploading "hidden" files.
The dependency license metadata cache is stored in a folder named `.licensed`. The "Check npm Dependencies" workflow
uploads the generated cache as a workflow artifact when the current cache is found to be outdated in order to facilitate
the update of the cache.
The `.` at the start of the `.licensed` folder name causes it to now not be uploaded to the workflow artifact. In order
to catch such problems, the workflow configures the "actions/upload-artifact" action to fail if no files were uploaded.
So the workflow now fails:
Error: No files were found with the provided path: .licenses/. No artifacts will be uploaded.
The problem is fixed by disabling the "actions/upload-artifact" action's new behavior via the `include-hidden-files`
input.
The unit and integration tests install various versions of Task.
The GitHub Actions macos-latest runner was recently changed from using x86 machines to Apple Silicon/ARM machines.
Task is now built for both architectures. However, this was not done for the older versions of Task previously used for the tests. The runner architecture switch caused the tests to start failing spuriously on the macos-latest GitHub Actions workflow run jobs:
```
FAIL __tests__/main.test.ts
installer tests
✕ Downloads version of Task if no matching version is installed (188 ms)
Gets the latest release of Task
✕ Gets the latest version of Task 2.5 using 2.5 and no matching version is installed (174 ms)
✕ Gets latest version of Task using 2.x and no matching version is installed (68 ms)
✕ Gets preview version of Task using 3.x and no matching version is installed (167 ms)
✕ Skips version computing when a valid semver is provided (164 ms)
● installer tests › Downloads version of Task if no matching version is installed
Failed to download version v2.6.0: Error: Unexpected HTTP response: 404
165 | core.debug(error.toString());
166 | }
> 167 | throw new Error(`Failed to download version ${version}: ${error}`);
| ^
168 | }
169 |
170 | // Extract
at src/installer.ts:167:11
at Generator.throw (<anonymous>)
at rejected (src/installer.ts:40:65)
● installer tests › Gets the latest release of Task › Gets the latest version of Task 2.5 using 2.5 and no matching version is installed
Failed to download version v2.5.2: Error: Unexpected HTTP response: 404
165 | core.debug(error.toString());
166 | }
> 167 | throw new Error(`Failed to download version ${version}: ${error}`);
| ^
168 | }
169 |
170 | // Extract
at src/installer.ts:167:11
at Generator.throw (<anonymous>)
at rejected (src/installer.ts:40:65)
● installer tests › Gets the latest release of Task › Gets latest version of Task using 2.x and no matching version is installed
Failed to download version v2.6.0: Error: Unexpected HTTP response: 404
165 | core.debug(error.toString());
166 | }
> 167 | throw new Error(`Failed to download version ${version}: ${error}`);
| ^
168 | }
169 |
170 | // Extract
at src/installer.ts:167:11
at Generator.throw (<anonymous>)
at rejected (src/installer.ts:40:65)
● installer tests › Gets the latest release of Task › Gets preview version of Task using 3.x and no matching version is installed
Failed to download version v3.0.0-preview1: Error: Unexpected HTTP response: 404
165 | core.debug(error.toString());
166 | }
> 167 | throw new Error(`Failed to download version ${version}: ${error}`);
| ^
168 | }
169 |
170 | // Extract
at src/installer.ts:167:11
at Generator.throw (<anonymous>)
at rejected (src/installer.ts:40:65)
● installer tests › Gets the latest release of Task › Skips version computing when a valid semver is provided
Failed to download version v3.0.0: Error: Unexpected HTTP response: 404
165 | core.debug(error.toString());
166 | }
> 167 | throw new Error(`Failed to download version ${version}: ${error}`);
| ^
168 | }
169 |
170 | // Extract
at src/installer.ts:167:11
at Generator.throw (<anonymous>)
at rejected (src/installer.ts:40:65)
```
```
Error: Failed to download version v3.4.1: Error: Unexpected HTTP response: 404
```
The obvious solution would be to pin the runner to the last x86 runner: macos-13 in the test runner workflows. However, GitHub phases out runners over time so this would not be a future proof solution. The chosen solution is to continue to use the Apple Silicon macos-latest runner and adjust the tests to use versions of Task for which an Apple Silicon build is available.
This was not possible for the integration test of the action's major version pinning capability. The reason is that a previous major version (2.x) must be used in this test in order to allow a consistent assertion, since the installed version would change over time if the latest major version was used. Apple Silicon builds are not available in the 2.x Task version series. For this reason, the integration test runner workflow is configured to skip that test on the macos-latest runner job. The Linux and Windows integration test runner jobs, as well as unit test (which is not constrained in this way due to using artificial tags data) will provide sufficient coverage for this capability.
The actions/setup-node GitHub Actions action is used to set up Node.js in the GitHub Actions runner machine.
The action supports obtaining the Node.js version to set up from the `engines.node` field of the package.json file.
This allows us to define the standardized version of Node.js for use by project contributors in a single place rather
than having to maintain multiple instances of that data.
The Dependabot service is used to keep the project dependencies updated.
Thanks to the project's high quality validation infrastructure, the human effort required to complete a trivial version
bump is minimal. However, some bumps may introduce breaking changes that would require a significant amount of effort to
accommodate, or are blocked by external tasks. In this case, the Dependabot pull request can't be merged, but should be
left open to track the need to perform the bump when it is feasible. This means that it should be expected that there
will be regularly be a small number of Dependabot pull requests left open in the repository over long periods of time.
The automated system is here to assist the human project maintainers, not as a tyrannical overlord, so this is the
system working exactly as intended.
By default, Dependabot is configured to stop submitting pull requests if it already has five open pull requests. This
means that if it happens that the accumulation of intentionally on-hold pull requests reaches that number, the project
stops receiving the easily handled trivial update PRs. This is very harmful because it results in the completely
unnecessary use of outdated dependencies, and unnecessary challenging large bumps when pull requests start being
submitted once more after the backlog is cleared.
The harmful default configuration is hereby overridden by configuring the maximum open pull request limit at 100. This
value was chosen as an arbitrary large number simply to functionally disable the limiting, rather than from any
expectation that the actual number of open PRs can ever reach that count.
As the primary maintainer of the project infrastructure, it is the responsibility of GitHub user per1234 to review and
merge the pull requests automatically submitted by Dependabot for bumps of outdated project dependencies.
Configuring Dependabot to automatically set the pull request assignment will slightly streamline that process.
Some of the assets use tools sourced from the npm software registry.
Previously, the version of the tools used was not controlled. This was problematic because:
- A different version of the tool may be used on the contributor's machine than on the CI runner, resulting in confusing
failures.
- The project is immediately subject to disruption or breakage resulting from a release of the tool.
---
These tools were installed via either of the following methods:
`npx <pkg>`
This approach has the following behaviors of interest:
https://docs.npmjs.com/cli/v8/commands/npx#description
> If any requested packages are not present in the local project dependencies, then they are installed to a folder in the npm cache, which is added to the PATH environment variable in the executed process.
> Package names provided without a specifier will be matched with whatever version exists in the local project. Package names with a specifier will only be considered a match if they have the exact same name and version as the local dependency.
This means that the version used was:
1. Whatever happens to be present in the local cache
2. The latest available version if it is not already present
`npm install --global <pkg>`
The latest available version of the package is used.
---
The new approach is to specify the version of the tools via the standard npm metadata files (package.json +
package-lock.json). This approach was chosen over the `npx <pkg>@<version>` alternative for the following reasons:
- Enables automated updates via Dependabot PRs
- Enables automated vulnerability alerts
- Separates dependency management from the asset contents (i.e., no need to mess with the taskfile or workflow on every
update)
- Matches how we are already managing Python dependencies (pyproject.toml + poetry.lock)
The "Sync Labels" workflow was originally developed with Arduino firmware repositories in mind. Those projects don't
have a lot of existing infrastructure and, since they are intended to be very approachable, the impact of adding
additional non-firmware files or folders (especially in the root) must be carefully considered.
In that context, a self-contained workflow is desirable. However, the situation is different in the inherently more
complex and infrastructure rich tooling projects, which are typically consumed only as a binary by users.
For this reason, an alternative standardized version of the "Sync Labels" workflow was produced, which utilizes npm to
manage its tool dependencies.
The code dependencies of this project and the code infrastructure are already managed via npm, which means the switch to
this superior version of the workflow can be made without the need to add any additional infrastructure.
This provides some significant benefits:
- Controlled updates via Dependabot PRs instead of being subject to immediate breakage resulting from a new tool release
- Enables automated vulnerability alerts
This is especially important for the github-label-sync tool, since it is making irreversible writes to the GitHub
repository.
In addition to helping others use Task in their GitHub Actions workflows, this project uses Task for its own development.
Most of the repository's GitHub Actions workflows use the approach of executing a task. This approach allows maintaining
a single set of commands that can easily be used locally by contributors in addition to their use by the CI system.
The "Check npm" workflow was an exception to this pattern, using commands directly coded into the workflow file. This
meant the contributor would need to study the workflow and run the multiple equivalent complex commands locally if they
wanted to validate changes to the npm configuration in advance of pushing, or troubleshoot a problem flagged by the
workflow.
We already have a Task-based version of the workflow and accompanying tasks, which are generally preferred for Arduino
tooling projects and especially appropriate for this specific project which is devoted to Task:
https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/check-npm-task.md
Those standardized assets are hereby used in this repository, moving the commands from the workflow to an npm:validate
task and switching the workflow to using that task.
This project uses Node.js 16. Previously, although the workflows that were more closely related to the code were
configured to use this version, other workflows which also use Node.js/npm were using whichever version happened to be
pre-installed on the GitHub Actions runner.
Since the contributor will be using Node.js 16 on their local machine for all development and maintenance operations,
use of a different version by the GitHub Actions runner might result in confusing differences in results between the
local machine and the CI system.
This is avoided by setting the Node.js version in all applicable workflows. Following the existing convention, this is
done via an environment variable at the top of the workflow in order to facilitate the process of updating to a new
standard version of Node.js in the project.
In addition to helping others use Task in their GitHub Actions workflows, this project uses Task for its own development.
Most of the repository's GitHub Actions workflows use the approach of executing a task. This approach allows maintaining
a single set of commands that can easily be used locally by contributors in addition to their use by the CI system.
The "Check TypeScript Configuration" workflow was an exception to this pattern, using commands directly coded into the
workflow file. This meant the contributor would need to study the workflow and run the multiple equivalent complex
commands locally if they wanted to validate changes to the TypeScript configuration in advance of pushing, or
troubleshoot a problem flagged by the workflow.
These commands are hereby moved to a ts:validate task and the workflow switched to using that task.
This is intended to be a straight across transfer of the existing commands (or their equivalents) to the task.
A task and GitHub Actions workflow are provided here for checking the license types of npm-managed project dependencies.
On every push and pull request that affects relevant files, the CI workflow will check:
- If the dependency licenses cache is up to date
- If any of the project's dependencies have an unapproved license type.
Approval can be based on:
- Universally allowed license type
- Individual dependency
The "Check npm" GitHub Actions workflow validates the repository's `package.json` npm manifest file against its JSON
schema to catch any problems with its data format.
In order to avoid duplication of content, JSON schemas may reference other schemas via the `$ref` keyword. The
`package.json` schema was recently updated to share resources with the jscpd configuration schema, which caused the
validation to start failing:
schema /home/runner/work/_temp/json-schema/package-json-schema.json is invalid
error: can't resolve reference https://json.schemastore.org/jscpd.json from id #
The solution is to configure the workflow to download that schema as well and also to provide its path to the avj-cli
validator via an `-r` flag.
GitHub Actions actions are used by the workflows to set up development tools in the runner workspace.
In order to facilitate updates to new versions of these tools, we set the version to be set up via environment variables
at the top of the workflow.
Since this variable definition is separate from the step using the action, it might not be immediately apparent to the
maintainer which version syntaxes are supported. For this reason, comments were added with the URL to the relevant
section of the consuming action's documentation. Previously, these URLs were made to point to the version of the
documentation that matched the version of the action in use by the workflow. Since we only use a major version ref, the
expectation was that this would only need to be updated rarely. However, it turned out that the major version bump cycle
is significantly shorter than expected. In addition, it is easy to forget the update because action version update PRs
are provided by Dependabot, which obviously won't update the URLs in the comments.
So it will be best to use a URL that points to the documentation at the tip of the default branch of the action
repository. The likelihood of the documentation provided by this URL not matching the behavior of the release version of
the action in use is likely less than it is for an outdated URL.
The "Check npm" GitHub Actions workflow validates the repository's `package.json` npm manifest file against its JSON
schema to catch any problems with its data format.
In order to avoid duplication of content, JSON schemas may reference other schemas via the `$ref` keyword. The
`package.json` schema was recently updated to share resources with the `.stylelintrc` and semantic-release configuration
schemas, which caused the validation to
start failing:
schema /home/runner/work/_temp/json-schema/package-json-schema.json is invalid
error: can't resolve reference https://json.schemastore.org/stylelintrc.json from id #
The solution is to configure the workflow to download that schema as well and also to provide its path to the avj-cli
validator via an `-r` flag.
The "Check npm" GitHub Actions workflow validates the repository's `package.json` npm manifest file against its JSON
schema to catch any problems with its data format.
In order to avoid duplication of content, JSON schemas may reference other schemas via the `$ref` keyword. The
`package.json` schema was recently updated to share resources with the `ava.json` schema, which caused the validation to
start failing:
schema /home/runner/work/_temp/json-schema/package-json-schema.json is invalid
error: can't resolve reference https://json.schemastore.org/ava.json from id #
The solution is to configure the workflow to download that schema as well and also to provide its path to the avj-cli
validator via an `-r` flag.