From bc51b338e568400474f7a212a758477f5550a47e Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Wed, 15 Sep 2021 18:57:02 -0700 Subject: [PATCH] Test against real-world Sass frameworks In a future commit, I'll update these jobs to only run just before release, since they're expected to be substantially less likely to fail and we don't want to waste cycles. For now, I have them runnning always to verify that they run successfully at least once. --- .github/workflows/ci.yml | 62 ++++++++++++++++++++++++++++++++ pubspec.yaml | 2 +- tool/grind.dart | 5 +-- tool/grind/frameworks.dart | 71 +++++++++++++++++++++++++++++++++++++ tool/grind/subpackages.dart | 15 ++------ tool/grind/utils.dart | 30 ++++++++++++---- 6 files changed, 163 insertions(+), 22 deletions(-) create mode 100644 tool/grind/frameworks.dart diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0fc4b98e4..058747772 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -142,6 +142,68 @@ jobs: --errors ambiguous-doc-reference,broken-link,deprecated --errors unknown-directive,unknown-macro,unresolved-doc-reference + bootstrap: + name: "Bootstrap ${{ matrix.bootstrap_version }}" + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + bootstrap_version: [4, 5] + + steps: + - uses: actions/checkout@v2 + - uses: dart-lang/setup-dart@v1 + - run: dart pub get + - run: dart pub run grinder fetch-bootstrap${{matrix.bootstrap_version}} + env: {GITHUB_BEARER_TOKEN: "${{ secrets.GITHUB_TOKEN }}"} + - name: Build + run: dart bin/sass.dart --quiet build/bootstrap/scss:build/bootstrap-output + + bourbon: + name: Bourbon + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - uses: dart-lang/setup-dart@v1 + - run: dart pub get + - run: dart pub run grinder fetch-bourbon + env: {GITHUB_BEARER_TOKEN: "${{ secrets.GITHUB_TOKEN }}"} + - name: Test + run: | + dart bin/sass.dart --quiet -I build/bourbon -I build/bourbon/spec/fixtures \ + build/bourbon/spec/fixtures:build/bourbon-output + + foundation: + name: Foundation + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - uses: dart-lang/setup-dart@v1 + - run: dart pub get + - run: dart pub run grinder fetch-foundation + env: {GITHUB_BEARER_TOKEN: "${{ secrets.GITHUB_TOKEN }}"} + # TODO(nweiz): Foundation has proper Sass tests, but they're currently not + # compatible with Dart Sass. Once they are, we should run those rather + # than just building the CSS output. + - name: Build + run: dart bin/sass.dart --quiet build/foundation-sites/assets:build/foundation-output + + bulma: + name: Bulma + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - uses: dart-lang/setup-dart@v1 + - run: dart pub get + - run: dart pub run grinder fetch-bulma + env: {GITHUB_BEARER_TOKEN: "${{ secrets.GITHUB_TOKEN }}"} + - name: Build + run: dart bin/sass.dart --quiet build/bulma/bulma.sass build/bulma-output.css + sanity_checks: name: Sanity checks runs-on: ubuntu-latest diff --git a/pubspec.yaml b/pubspec.yaml index 4a38f57d1..e34897ee9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -34,7 +34,7 @@ dependencies: dev_dependencies: analyzer: ^2.2.0 archive: ^3.1.2 - cli_pkg: ^1.3.0 + cli_pkg: ^1.7.0 crypto: ^3.0.0 dart_style: ^2.0.0 grinder: ^0.9.0 diff --git a/tool/grind.dart b/tool/grind.dart index 8060773be..d3d4d0f8c 100644 --- a/tool/grind.dart +++ b/tool/grind.dart @@ -15,6 +15,7 @@ import 'grind/synchronize.dart'; export 'grind/bazel.dart'; export 'grind/benchmark.dart'; +export 'grind/frameworks.dart'; export 'grind/sanity_check.dart'; export 'grind/subpackages.dart'; export 'grind/synchronize.dart'; @@ -34,8 +35,8 @@ void main(List args) { as Map; pkg.npmReadme.fn = () => _readAndResolveMarkdown("package/README.npm.md"); pkg.standaloneName.value = "dart-sass"; - pkg.githubUser.fn = () => Platform.environment["GH_USER"]!; - pkg.githubPassword.fn = () => Platform.environment["GH_TOKEN"]!; + pkg.githubUser.fn = () => Platform.environment["GH_USER"]; + pkg.githubPassword.fn = () => Platform.environment["GH_TOKEN"]; pkg.githubReleaseNotes.fn = () => "To install Sass ${pkg.version}, download one of the packages below " diff --git a/tool/grind/frameworks.dart b/tool/grind/frameworks.dart new file mode 100644 index 000000000..400ca8c9a --- /dev/null +++ b/tool/grind/frameworks.dart @@ -0,0 +1,71 @@ +// Copyright 2021 Google Inc. Use of this source code is governed by an +// MIT-style license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'dart:convert'; + +import 'package:grinder/grinder.dart'; +import 'package:http/http.dart' as http; + +import 'utils.dart'; + +@Task('Download Bootstrap 5.x for testing purposes.') +Future fetchBootstrap5() => _getLatestRelease('twbs/bootstrap'); + +@Task('Download Bootstrap 4.x for testing purposes.') +Future fetchBootstrap4() => + _getLatestRelease('twbs/bootstrap', pattern: RegExp(r'^v4\.')); + +@Task('Download Bourbon for testing purposes.') +Future fetchBourbon() => _getLatestRelease('thoughtbot/bourbon'); + +@Task('Download Foundation for testing purposes.') +Future fetchFoundation() => + _getLatestRelease('foundation/foundation-sites'); + +@Task('Download Bulma for testing purposes.') +Future fetchBulma() => _getLatestRelease('jgthms/bulma'); + +/// Clones the latest release of the given GitHub repository [slug]. +/// +/// If [pattern] is passed, this will clone the latest release that matches that +/// pattern. +Future _getLatestRelease(String slug, {Pattern? pattern}) async { + await cloneOrCheckout('git://github.com/$slug', + await _findLatestRelease(slug, pattern: pattern)); +} + +/// Returns the tag name of the latest release for the given GitHub repository +/// [slug]. +/// +/// If [pattern] is passed, this will find the latest release that matches that +/// pattern. +Future _findLatestRelease(String slug, {Pattern? pattern}) async { + var releases = await _fetchReleases(slug); + if (pattern == null) return releases[0]['tag_name'] as String; + + var page = 1; + while (releases.isNotEmpty) { + for (var release in releases) { + var tagName = release['tag_name'] as String; + if (pattern.allMatches(tagName).isNotEmpty) return tagName; + } + + page++; + releases = await _fetchReleases(slug, page: page); + } + + fail("Couldn't find a release of $slug matching $pattern."); +} + +/// Fetches the GitHub releases page for the repo at [slug]. +Future>> _fetchReleases(String slug, + {int page = 1}) async { + var result = json.decode(await http.read( + Uri.parse("https://api.github.com/repos/$slug/releases?page=$page"), + headers: { + "accept": "application/vnd.github.v3+json", + "authorization": githubAuthorization + })) as List; + return result.cast>(); +} diff --git a/tool/grind/subpackages.dart b/tool/grind/subpackages.dart index e2d946fe5..e01f42356 100644 --- a/tool/grind/subpackages.dart +++ b/tool/grind/subpackages.dart @@ -12,6 +12,8 @@ import 'package:path/path.dart' as p; import 'package:pubspec_parse/pubspec_parse.dart'; import 'package:yaml/yaml.dart'; +import 'utils.dart'; + /// The path in which pub expects to find its credentials file. final String _pubCredentialsPath = () { // This follows the same logic as pub: @@ -30,17 +32,6 @@ final String _pubCredentialsPath = () { return p.join(cacheDir, 'credentials.json'); }(); -/// Returns the HTTP basic authentication Authorization header from the -/// environment. -String get _githubAuthorization { - var bearerToken = pkg.githubBearerToken.value; - return bearerToken != null - ? "Bearer $bearerToken" - : "Basic " + - base64.encode(utf8 - .encode(pkg.githubUser.value + ':' + pkg.githubPassword.value)); -} - @Task('Deploy sub-packages to pub.') Future deploySubPackages() async { // Write pub credentials @@ -86,7 +77,7 @@ Future deploySubPackages() async { headers: { "accept": "application/vnd.github.v3+json", "content-type": "application/json", - "authorization": _githubAuthorization + "authorization": githubAuthorization }, body: jsonEncode({ "ref": "refs/tags/${pubspec.name}/${pubspec.version}", diff --git a/tool/grind/utils.dart b/tool/grind/utils.dart index cd33f0cff..055b554c9 100644 --- a/tool/grind/utils.dart +++ b/tool/grind/utils.dart @@ -3,6 +3,7 @@ // https://opensource.org/licenses/MIT. import 'dart:async'; +import 'dart:convert'; import 'dart:io'; import 'package:cli_pkg/cli_pkg.dart' as pkg; @@ -17,6 +18,17 @@ final sassBotEnvironment = RunOptions(environment: { "GIT_COMMITTER_EMAIL": pkg.botEmail.value }); +/// Returns the HTTP basic authentication Authorization header from the +/// environment. +String get githubAuthorization { + var bearerToken = pkg.githubBearerToken.value; + return bearerToken != null + ? "Bearer $bearerToken" + : "Basic " + + base64.encode(utf8 + .encode(pkg.githubUser.value + ':' + pkg.githubPassword.value)); +} + /// Ensure that the `build/` directory exists. void ensureBuild() { Directory('build').createSync(recursive: true); @@ -47,18 +59,22 @@ Future cloneOrCheckout(String url, String ref) async { var path = p.join("build", name); - if (Directory(p.join(path, '.git')).existsSync()) { - log("Updating $url"); - await runAsync("git", - arguments: ["fetch", "origin"], workingDirectory: path); - } else { + if (!Directory(p.join(path, '.git')).existsSync()) { delete(Directory(path)); - await runAsync("git", arguments: ["clone", url, path]); + await runAsync("git", arguments: ["init", path]); await runAsync("git", arguments: ["config", "advice.detachedHead", "false"], workingDirectory: path); + await runAsync("git", + arguments: ["remote", "add", "origin", url], workingDirectory: path); + } else { + log("Updating $url"); } - await runAsync("git", arguments: ["checkout", ref], workingDirectory: path); + + await runAsync("git", + arguments: ["fetch", "origin", "--depth=1", ref], workingDirectory: path); + await runAsync("git", + arguments: ["checkout", "FETCH_HEAD"], workingDirectory: path); log(""); return path;