diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0c8f567a8..f60591799 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -194,6 +194,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 + double_check: name: Double-check runs-on: ubuntu-latest diff --git a/pubspec.yaml b/pubspec.yaml index 1371deaa9..1005de674 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;