From af21aead9ea7a99d0810977c2f61629ed2eee055 Mon Sep 17 00:00:00 2001 From: Leo Farias Date: Thu, 21 Sep 2023 23:42:24 -0400 Subject: [PATCH] wip --- .github/actions/prepare/action.yml | 7 --- .github/actions/test/action.yml | 21 ++++--- .github/workflows/release.yml | 9 +-- .github/workflows/test.yml | 4 +- lib/src/utils/console_utils.dart | 9 +++ pubspec.lock | 4 +- pubspec.yaml | 2 + tool/fvm.template.rb | 23 ++++++++ tool/grind.dart | 67 ++++++++++----------- tool/homebrew.dart | 95 ++++++++++++++++++++++++++++++ 10 files changed, 183 insertions(+), 58 deletions(-) create mode 100644 tool/fvm.template.rb create mode 100644 tool/homebrew.dart diff --git a/.github/actions/prepare/action.yml b/.github/actions/prepare/action.yml index 31592fc0..0568bcc7 100644 --- a/.github/actions/prepare/action.yml +++ b/.github/actions/prepare/action.yml @@ -28,13 +28,6 @@ runs: run: dart pub get shell: bash - - run: dart pub global activate grinder - shell: bash - - - name: Build version - run: dart pub global run grinder:grinder build-version - shell: bash - - name: Analyze run: dart analyze --fatal-infos --fatal-warnings . shell: bash diff --git a/.github/actions/test/action.yml b/.github/actions/test/action.yml index bbf53296..00e4507b 100644 --- a/.github/actions/test/action.yml +++ b/.github/actions/test/action.yml @@ -1,13 +1,20 @@ -name: "Test Step" -description: "Tests the project" +name: "Run tests" +description: "Grind tasks for testing" + +inputs: + with-coverage: + description: "Generate coverage reports" + required: false + default: "false" runs: using: "composite" steps: - - name: Generate git cache - run: ./bin/fvm.dart install master + - name: Run tests + run: dart pub run grinder test shell: bash - - name: Run tests - run: dart pub global run grinder:grinder test - shell: bash \ No newline at end of file + - name: Generate coverage report + run: dart pub run grinder coverage + shell: bash + if: ${{ inputs.with-coverage == 'true' }} \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ddfc5b19..e61346b7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -43,8 +43,9 @@ jobs: - name: Prepare environment uses: ./.github/actions/prepare - - name: Deploy Chocolatey (Windows) - run: dart pub run grinder pkg-chocolatey-deploy + + # - name: Deploy Chocolatey (Windows) + # run: dart pub run grinder pkg-chocolatey-deploy deploy-homebrew: name: Deploy Homebrew @@ -61,8 +62,8 @@ jobs: - name: Prepare environment uses: ./.github/actions/prepare - - name: Deploy to Homebrew - run: dart pub run grinder pkg-homebrew-update + # - name: Deploy to Homebrew + # run: dart pub run grinder pkg-homebrew-update deploy-docker: name: Docker Deploy (latest) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1efc2261..1607d4d2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -21,7 +21,7 @@ concurrency: jobs: test: - name: Test + name: Run tests with coverage runs-on: ubuntu-latest steps: - name: Checkout @@ -37,6 +37,8 @@ jobs: - name: Run tests uses: ./.github/actions/test + with: + with-coverage: 'true' - name: Upload coverage reports to Codecov uses: codecov/codecov-action@v3 diff --git a/lib/src/utils/console_utils.dart b/lib/src/utils/console_utils.dart index ad11d194..f8da2142 100644 --- a/lib/src/utils/console_utils.dart +++ b/lib/src/utils/console_utils.dart @@ -91,3 +91,12 @@ void switchLineMode(bool active, List args) { return; } } + +Future isCommandAvailable(String command) async { + try { + final result = await Process.run(command, ['--version']); + return result.exitCode == 0; + } catch (e) { + return false; + } +} diff --git a/pubspec.lock b/pubspec.lock index dc202217..06a664ec 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -122,7 +122,7 @@ packages: source: hosted version: "1.6.3" crypto: - dependency: transitive + dependency: "direct dev" description: name: crypto sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab @@ -194,7 +194,7 @@ packages: source: hosted version: "0.9.4" http: - dependency: transitive + dependency: "direct dev" description: name: http sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" diff --git a/pubspec.yaml b/pubspec.yaml index cf0d9265..49a51aa4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -34,4 +34,6 @@ dev_dependencies: grinder: ^0.9.4 test: ^1.24.4 lints: ^2.1.1 + crypto: ^3.0.3 + http: ^1.1.0 diff --git a/tool/fvm.template.rb b/tool/fvm.template.rb new file mode 100644 index 00000000..589b382c --- /dev/null +++ b/tool/fvm.template.rb @@ -0,0 +1,23 @@ +class Fvm < Formula + desc "Flutter Version Management: A CLI to manage Flutter SDK versions" + homepage "https://github.com/leoafarias/fvm" + version "{{VERSION}}" + + on_macos do + if Hardware::CPU.arm? + url "{{MACOS_ARM64_URL}}" + sha256 "{{MACOS_ARM64_SHA256}}" + else + url "{{MACOS_X64_URL}}" + sha256 "{{MACOS_X64_SHA256}}" + end + end + + def install + bin.install "fvm" + end + + test do + assert_match "FVM #{version}", shell_output("#{bin}/fvm --version").strip + end +end \ No newline at end of file diff --git a/tool/grind.dart b/tool/grind.dart index 23e82f83..c1e81b56 100644 --- a/tool/grind.dart +++ b/tool/grind.dart @@ -9,46 +9,38 @@ import 'package:pub_semver/pub_semver.dart'; import 'package:pubspec2/pubspec2.dart'; import '../test/testing_helpers/prepare_test_environment.dart'; +import 'homebrew.dart'; + +const _packageName = 'fvm'; +const owner = 'leoafarias'; +const repo = 'fvm'; void main(List args) { - pkg.name.value = 'fvm'; - pkg.humanName.value = 'fvm'; - pkg.githubUser.value = 'fluttertools'; + pkg.name.value = _packageName; + pkg.humanName.value = _packageName; + pkg.githubUser.value = owner; pkg.homebrewRepo.value = 'leoafarias/homebrew-fvm'; pkg.addAllTasks(); + addTask(homebrewTask()); + grind(args); } @Task('Builds the version file') -// Allows to pass a version argument -// Example: grind build-version --version 3.0.0 -buildVersion() async { - TaskArgs args = context.invocation.arguments; - - String? versionArg = args.getOption('version'); +Future buildVersion() async { + final args = context.invocation.arguments; + final versionArg = args.getOption('version'); - // Get the pubspec file final pubspec = await PubSpec.load(Directory.current); - - // Get the version - Version? currentVersion = pubspec.version; - Version? version = pubspec.version; if (versionArg != null) { version = Version.parse(versionArg); } - log(currentVersion.toString()); - if (version != pubspec.version) { - // change the dependencies to a single path dependency on project 'foo' - var newPubSpec = pubspec.copy( - version: version, - ); - - // save it + var newPubSpec = pubspec.copy(version: version); await newPubSpec.save(Directory.current); } @@ -60,11 +52,11 @@ buildVersion() async { versionFile.createSync(recursive: true); } -// Write the following: -// const packageVersion = '2.4.1'; - versionFile.writeAsStringSync( - "const packageVersion = '$version';", - ); +// Add comment on top of the const that this file is generated by this command + String fileContent = '// GENERATED CODE - DO NOT MODIFY BY HAND\n\n '; + fileContent += "const packageVersion = '$version';"; + + versionFile.writeAsStringSync(fileContent); log('Version $version written to version.g.dart'); } @@ -97,27 +89,27 @@ Future getReleases() async { } @Task('Prepare test environment') -Future prepareTest() async { +@Depends(buildVersion) +Future testSetup() async { final testDir = Directory(getTempTestDir()); if (testDir.existsSync()) { testDir.deleteSync(recursive: true); } + + runDartScript('bin/fvm.dart', arguments: ['install', 'stable']); } -@Task('Test') -@Depends(buildVersion, prepareTest) +@Task('Run tests') +@Depends(testSetup) Future test() async { - runDartScript('bin/fvm.dart', arguments: ['install', '2.2.0']); - await runAsync('dart', arguments: ['test', '--coverage=coverage']); - // Run collectCoverage within a grind task - await collectCoverage(); } -@Task('Gather coverage and generate report') -@Depends(test) -Future collectCoverage() async { +@Task('Get coverage') +Future coverage() async { await runAsync('dart', arguments: ['pub', 'global', 'activate', 'coverage']); + + // Format coverage await runAsync( 'dart', arguments: [ @@ -133,6 +125,7 @@ Future collectCoverage() async { ], ); + // Generate html await runAsync( 'genhtml', arguments: ['coverage/lcov.info', '-o', 'coverage/html'], diff --git a/tool/homebrew.dart b/tool/homebrew.dart new file mode 100644 index 00000000..b6fca31d --- /dev/null +++ b/tool/homebrew.dart @@ -0,0 +1,95 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:crypto/crypto.dart'; +import 'package:fvm/src/utils/http.dart'; +import 'package:grinder/grinder.dart'; +import 'package:http/http.dart' as http; +import 'package:path/path.dart' as path; + +import 'grind.dart'; + +GrinderTask homebrewTask() => GrinderTask( + 'homebrew-formula', + taskFunction: _homebrewFormula, + ); + +Future _homebrewFormula() async { + final githubToken = Platform.environment['GITHUB_TOKEN'] ?? ''; + final args = context.invocation.arguments; + final versionArg = args.getOption('version'); + + if (versionArg == null) { + throw Exception('Version is required'); + } + + final url = Uri.parse( + 'https://api.github.com/repos/$owner/$repo/releases/tags/$versionArg'); + final headers = { + if (githubToken.isNotEmpty) 'Authorization': 'token $githubToken', + 'Accept': 'application/vnd.github.v3+json', + }; + + final response = await fetch(url.toString(), headers: headers); + + final Map release = json.decode(response); + final List assets = release['assets']; + final Map assetData = {}; + + for (final asset in assets) { + final assetUrl = Uri.parse(asset['browser_download_url']); + final filename = path.basename(assetUrl.path); + + if (!filename.contains('macos-x64') && !filename.contains('macos-arm64')) { + continue; + } + + final sha256Hash = await _downloadFile(assetUrl, filename, headers); + + if (sha256Hash.isNotEmpty) { + assetData[filename] = { + 'url': asset['browser_download_url'], + 'sha256': sha256Hash, + }; + } + } + + final template = File('tool/fvm.template.rb').readAsStringSync(); + + final macosX64 = assetData['fvm-$versionArg-macos-x64.tar.gz']; + final macosArm64 = assetData['fvm-$versionArg-macos-arm64.tar.gz']; + + final formula = template + .replaceAll('{{VERSION}}', versionArg) + .replaceAll('{{MACOS_X64_URL}}', macosX64['url']) + .replaceAll('{{MACOS_X64_SHA256}}', macosX64['sha256']) + .replaceAll('{{MACOS_ARM64_URL}}', macosArm64['url']) + .replaceAll( + '{{MACOS_ARM64_SHA256}}', + macosArm64['sha256'], + ); + + final file = File('fvm.rb'); + file.writeAsStringSync(formula); +} + +Future _downloadFile( + Uri url, + String filename, + Map headers, +) async { + final response = await http.get(url, headers: headers); + if (response.statusCode == 200) { + final bytes = response.bodyBytes; + await File(filename).writeAsBytes(bytes); + print('Downloaded: $filename'); + + // Calculate SHA-256 hash + final sha256Hash = sha256.convert(bytes).toString(); + print('SHA-256 Hash: $sha256Hash'); + return sha256Hash; + } else { + print('Failed to download $filename: ${response.statusCode}'); + return ''; + } +}