Skip to content
This repository was archived by the owner on Jul 16, 2023. It is now read-only.

feat: implement --sdk-path CLI option to use when running as compiled executable #430

Merged
merged 16 commits into from
Oct 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## Unreleased

* CLI now can be compiled to and used as compiled executable.

## 4.5.0

* feat: add static code diagnostics `avoid-nested-conditional-expressions`, `prefer-correct-identifier-length`, `prefer-correct-type-name`, `prefer-first`, `prefer-last`.
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ Usage: metrics [arguments...] <directories>

--root-folder=<./> Root folder.
(defaults to current directory)
--sdk-path=<directory-path> Dart SDK directory path. Should be provided only when you run the application as compiled executable(https://dart.dev/tools/dart-compile#exe) and automatic Dart SDK path detection fails.
--exclude=<{/**.g.dart,/**.template.dart}> File paths in Glob syntax to be exclude.
(defaults to "{/**.g.dart,/**.template.dart}")

Expand Down
8 changes: 5 additions & 3 deletions lib/src/analyzers/lint_analyzer/lint_analyzer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,11 @@ class LintAnalyzer {
Future<Iterable<LintFileReport>> runCliAnalysis(
Iterable<String> folders,
String rootFolder,
LintConfig config,
) async {
final collection = createAnalysisContextCollection(folders, rootFolder);
LintConfig config, {
String? sdkPath,
}) async {
final collection =
createAnalysisContextCollection(folders, rootFolder, sdkPath);

final analyzerResult = <LintFileReport>[];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,11 @@ class UnusedFilesAnalyzer {
Future<Iterable<UnusedFilesFileReport>> runCliAnalysis(
Iterable<String> folders,
String rootFolder,
UnusedFilesConfig config,
) async {
final collection = createAnalysisContextCollection(folders, rootFolder);
UnusedFilesConfig config, {
String? sdkPath,
}) async {
final collection =
createAnalysisContextCollection(folders, rootFolder, sdkPath);

final unusedFiles = <String>{};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,11 @@ class UnusedL10nAnalyzer {
Future<Iterable<UnusedL10nFileReport>> runCliAnalysis(
Iterable<String> folders,
String rootFolder,
UnusedL10nConfig config,
) async {
final collection = createAnalysisContextCollection(folders, rootFolder);
UnusedL10nConfig config, {
String? sdkPath,
}) async {
final collection =
createAnalysisContextCollection(folders, rootFolder, sdkPath);

final localizationUsages = <ClassElement, Set<String>>{};

Expand Down
10 changes: 2 additions & 8 deletions lib/src/cli/commands/analyze.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,6 @@ class AnalyzeCommand extends BaseCommand {
_addFlags();
}

@override
void validateCommand() {
validateRootFolderExist();
validateTargetDirectories();
}

@override
Future<void> runCommand() async {
final parsedArgs = ParsedArguments(
Expand All @@ -56,6 +50,7 @@ class AnalyzeCommand extends BaseCommand {
argResults.rest,
argResults[FlagNames.rootFolder] as String,
config,
sdkPath: findSdkPath(),
);

await _analyzer
Expand Down Expand Up @@ -96,8 +91,7 @@ class AnalyzeCommand extends BaseCommand {
void _addFlags() {
_usesReporterOption();
_usesMetricsThresholdOptions();
usesRootFolderOption();
usesExcludeOption();
addCommonFlags();
_usesExitOption();
}

Expand Down
43 changes: 40 additions & 3 deletions lib/src/cli/commands/base_command.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import 'package:path/path.dart';

import '../exceptions/arguments_validation_exceptions.dart';
import '../models/flag_names.dart';
import '../utils/detect_sdk_path.dart';

abstract class BaseCommand extends Command<void> {
@override
Expand All @@ -26,12 +27,15 @@ abstract class BaseCommand extends Command<void> {
@override
Future<void> run() => _verifyThenRunCommand();

@protected
void validateCommand();

@protected
Future<void> runCommand();

void validateCommand() {
validateRootFolderExist();
validateSdkPath();
validateTargetDirectories();
}

void usesRootFolderOption() {
argParser
..addSeparator('')
Expand All @@ -43,6 +47,15 @@ abstract class BaseCommand extends Command<void> {
);
}

void usesSdkPathOption() {
argParser.addOption(
FlagNames.sdkPath,
help:
'Dart SDK directory path. Should be provided only when you run the application as compiled executable(https://dart.dev/tools/dart-compile#exe) and automatic Dart SDK path detection fails.',
valueHelp: 'directory-path',
);
}

void usesExcludeOption() {
argParser.addOption(
FlagNames.exclude,
Expand All @@ -62,6 +75,16 @@ abstract class BaseCommand extends Command<void> {
}
}

void validateSdkPath() {
final sdkPath = argResults[FlagNames.sdkPath] as String?;
if (sdkPath != null && !Directory(sdkPath).existsSync()) {
final _exceptionMessage =
'Dart SDK path $sdkPath does not exist or not a directory.';

throw InvalidArgumentException(_exceptionMessage);
}
}

void validateTargetDirectories() {
if (argResults.rest.isEmpty) {
const _exceptionMessage =
Expand All @@ -83,6 +106,20 @@ abstract class BaseCommand extends Command<void> {
}
}

void addCommonFlags() {
usesRootFolderOption();
usesSdkPathOption();
usesExcludeOption();
}

String? findSdkPath() =>
argResults[FlagNames.sdkPath] as String? ??
detectSdkPath(
Platform.executable,
Platform.environment,
platformIsWindows: Platform.isWindows,
);

Future<void> _verifyThenRunCommand() async {
try {
validateCommand();
Expand Down
10 changes: 2 additions & 8 deletions lib/src/cli/commands/check_unused_files.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,6 @@ class CheckUnusedFilesCommand extends BaseCommand {
_addFlags();
}

@override
void validateCommand() {
validateRootFolderExist();
validateTargetDirectories();
}

@override
Future<void> runCommand() async {
final rootFolder = argResults[FlagNames.rootFolder] as String;
Expand All @@ -43,6 +37,7 @@ class CheckUnusedFilesCommand extends BaseCommand {
folders,
rootFolder,
config,
sdkPath: findSdkPath(),
);

await _analyzer
Expand All @@ -55,8 +50,7 @@ class CheckUnusedFilesCommand extends BaseCommand {

void _addFlags() {
_usesReporterOption();
usesRootFolderOption();
usesExcludeOption();
addCommonFlags();
}

void _usesReporterOption() {
Expand Down
10 changes: 2 additions & 8 deletions lib/src/cli/commands/check_unused_l10n.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,6 @@ class CheckUnusedL10nCommand extends BaseCommand {
_addFlags();
}

@override
void validateCommand() {
validateRootFolderExist();
validateTargetDirectories();
}

@override
Future<void> runCommand() async {
final rootFolder = argResults[FlagNames.rootFolder] as String;
Expand All @@ -48,6 +42,7 @@ class CheckUnusedL10nCommand extends BaseCommand {
folders,
rootFolder,
config,
sdkPath: findSdkPath(),
);

return _analyzer
Expand All @@ -61,8 +56,7 @@ class CheckUnusedL10nCommand extends BaseCommand {
void _addFlags() {
_usesL10nClassPatternOption();
_usesReporterOption();
usesRootFolderOption();
usesExcludeOption();
addCommonFlags();
}

void _usesReporterOption() {
Expand Down
1 change: 1 addition & 0 deletions lib/src/cli/models/flag_names.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ class FlagNames {
static const reporter = 'reporter';
static const exclude = 'exclude';
static const rootFolder = 'root-folder';
static const sdkPath = 'sdk-path';

static const consoleReporter = ConsoleReporter.id;
static const consoleVerboseReporter = ConsoleReporter.verboseId;
Expand Down
34 changes: 34 additions & 0 deletions lib/src/cli/utils/detect_sdk_path.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import 'dart:io';

import 'package:path/path.dart';

String? detectSdkPath(
String platformExecutable,
Map<String, String> platformEnvironment, {
required bool platformIsWindows,
}) {
String? sdkPath;
// When running as compiled executable (built with `dart compile exe`) we must
// pass Dart SDK path when we create analysis context. So we try to detect Dart SDK path
// from system %PATH% environment variable.
//
// See
// https://github.com/dart-code-checker/dart-code-metrics/issues/385
// https://github.com/dart-code-checker/dart-code-metrics/pull/430
const dartExeFileName = 'dart.exe';

if (platformIsWindows &&
!platformExecutable.toLowerCase().endsWith(dartExeFileName)) {
final paths = platformEnvironment['PATH']?.split(';') ?? [];
final dartExePath = paths.firstWhere(
(pathEntry) => File(join(pathEntry, dartExeFileName)).existsSync(),
orElse: () => '',
);
if (dartExePath.isNotEmpty) {
// dart.exe usually is located in %SDK_PATH%\bin directory so let's use parent directory name.
sdkPath = dirname(dartExePath);
}
}

return sdkPath;
}
2 changes: 2 additions & 0 deletions lib/src/utils/analyzer_utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ import 'package:path/path.dart';
AnalysisContextCollection createAnalysisContextCollection(
Iterable<String> folders,
String rootFolder,
String? sdkPath,
) {
final resourceProvider = PhysicalResourceProvider.INSTANCE;

return AnalysisContextCollectionImpl(
sdkPath: sdkPath,
includedPaths:
folders.map((path) => normalize(join(rootFolder, path))).toList(),
resourceProvider: resourceProvider,
Expand Down
1 change: 1 addition & 0 deletions test/cli/commands/analyze_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const _usage = 'Collect code metrics, rules and anti-patterns violations.\n'
'\n'
' --root-folder=<./> Root folder.\n'
' (defaults to current directory)\n'
' --sdk-path=<directory-path> Dart SDK directory path. Should be provided only when you run the application as compiled executable(https://dart.dev/tools/dart-compile#exe) and automatic Dart SDK path detection fails.\n'
' --exclude=<{/**.g.dart,/**.template.dart}> File paths in Glob syntax to be exclude.\n'
' (defaults to "{/**.g.dart,/**.template.dart}")\n'
'\n'
Expand Down
65 changes: 60 additions & 5 deletions test/cli/commands/base_command_test.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import 'dart:io';

import 'package:args/args.dart';
import 'package:dart_code_metrics/src/cli/commands/base_command.dart';
import 'package:dart_code_metrics/src/cli/exceptions/arguments_validation_exceptions.dart';
import 'package:mocktail/mocktail.dart';
import 'package:test/test.dart';

class DirectoryMock extends Mock implements Directory {}

void main() {
group('BaseCommand', () {
final result = ArgResultsMock();
Expand Down Expand Up @@ -34,6 +38,62 @@ void main() {
)),
);
});

test(
"should throw if 'sdk-path' directory is specified but doesn't exist",
() {
when(() => result['sdk-path'] as String).thenReturn('SDK_PATH');
IOOverrides.runZoned(
() {
expect(
command.validateSdkPath,
throwsA(predicate(
(e) =>
e is InvalidArgumentException &&
e.message ==
'Dart SDK path SDK_PATH does not exist or not a directory.',
)),
);
},
createDirectory: (path) {
final directory = DirectoryMock();
when(directory.existsSync).thenReturn(false);

return directory;
},
);
},
);

test(
"should not detect sdk path if 'sdk-path' option is specified",
() {
when(() => result['sdk-path'] as String).thenReturn('SDK_PATH');

expect(command.findSdkPath(), 'SDK_PATH');
},
);

test(
"should not throw on 'validateCommand' call if correct options passed",
() {
when(() => result['root-folder'] as String).thenReturn('');
when(() => result['sdk-path'] as String).thenReturn('');
when(() => result.rest).thenReturn(['']);

IOOverrides.runZoned(
() {
expect(command.validateCommand, returnsNormally);
},
createDirectory: (path) {
final directory = DirectoryMock();
when(directory.existsSync).thenReturn(true);

return directory;
},
);
},
);
});
}

Expand All @@ -53,11 +113,6 @@ class TestCommand extends BaseCommand {
@override
String get description => 'empty';

@override
void validateCommand() {
throw UnimplementedError();
}

@override
Future<void> runCommand() {
throw UnimplementedError();
Expand Down
1 change: 1 addition & 0 deletions test/cli/commands/check_unused_files_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const _usage = 'Check unused *.dart files.\n'
'\n'
' --root-folder=<./> Root folder.\n'
' (defaults to current directory)\n'
' --sdk-path=<directory-path> Dart SDK directory path. Should be provided only when you run the application as compiled executable(https://dart.dev/tools/dart-compile#exe) and automatic Dart SDK path detection fails.\n'
' --exclude=<{/**.g.dart,/**.template.dart}> File paths in Glob syntax to be exclude.\n'
' (defaults to "{/**.g.dart,/**.template.dart}")\n'
'\n'
Expand Down
Loading