From 5e8d4f6ca32a5d089ccc0b72686fbde70e5da74f Mon Sep 17 00:00:00 2001 From: eymeric Date: Mon, 23 Oct 2023 13:29:30 +0200 Subject: [PATCH 1/5] add the possibility to set a build_extensions option in the build.yaml --- .../lib/src/builder_options.dart | 10 ++- .../lib/src/builders/mappable_builder.dart | 11 ++- .../dart_mappable_builder/lib/src/utils.dart | 79 +++++++++++++++++++ .../test/simple_model_test.dart | 11 +++ 4 files changed, 104 insertions(+), 7 deletions(-) diff --git a/packages/dart_mappable_builder/lib/src/builder_options.dart b/packages/dart_mappable_builder/lib/src/builder_options.dart index 0443c940..4541a8da 100644 --- a/packages/dart_mappable_builder/lib/src/builder_options.dart +++ b/packages/dart_mappable_builder/lib/src/builder_options.dart @@ -3,6 +3,10 @@ import 'package:dart_mappable/dart_mappable.dart'; import 'utils.dart'; +const _defaultExtensions = { + '.dart': ['.mapper.dart', '.init.dart'] +}; + /// The builder options for a specific library class MappableOptions { final CaseStyle? caseStyle; @@ -13,6 +17,7 @@ class MappableOptions { final InitializerScope? initializerScope; final int? lineLength; final Map renameMethods; + final Map> buildExtensions; MappableOptions({ this.caseStyle, @@ -23,6 +28,7 @@ class MappableOptions { this.initializerScope, this.lineLength, this.renameMethods = const {}, + this.buildExtensions = _defaultExtensions, }); MappableOptions.parse(Map options) @@ -36,7 +42,9 @@ class MappableOptions { initializerScope = null, lineLength = options['lineLength'] as int? ?? options['line_length'] as int?, - renameMethods = toMap(options['renameMethods'] ?? {}); + renameMethods = toMap(options['renameMethods'] ?? {}), + buildExtensions = + validatedBuildExtensionsFrom(options, _defaultExtensions); MappableOptions apply(MappableOptions? options, {bool forceJoin = true}) { if (options == null) return this; diff --git a/packages/dart_mappable_builder/lib/src/builders/mappable_builder.dart b/packages/dart_mappable_builder/lib/src/builders/mappable_builder.dart index 704579b1..83c141e8 100644 --- a/packages/dart_mappable_builder/lib/src/builders/mappable_builder.dart +++ b/packages/dart_mappable_builder/lib/src/builders/mappable_builder.dart @@ -50,9 +50,7 @@ class MappableBuilder implements Builder { } @override - Map> get buildExtensions => const { - '.dart': ['.mapper.dart', '.init.dart'] - }; + Map> get buildExtensions => options.buildExtensions; Future createMapperGroup(BuildStep buildStep) async { var entryLib = await buildStep.inputLibrary; @@ -95,16 +93,17 @@ class MappableBuilder implements Builder { var output = await Future.wait(generators.map((g) => g.generate())); + final outputId = buildStep.allowedOutputs.first; var source = DartFormatter(pageWidth: options.lineLength ?? 80).format( '// coverage:ignore-file\n' '// GENERATED CODE - DO NOT MODIFY BY HAND\n' '// ignore_for_file: type=lint\n' '// ignore_for_file: unused_element, unnecessary_cast\n' '// ignore_for_file: strict_raw_type, inference_failure_on_untyped_parameter\n\n' - 'part of \'${p.basename(buildStep.inputId.uri.toString())}\';\n\n' + 'part of \'${uriOfPartial(buildStep.inputId, outputId)}\';\n\n' '${output.join('\n\n')}\n' //, ); - var outputId = buildStep.inputId.changeExtension('.mapper.dart'); + await buildStep.writeAsString(outputId, source); } @@ -138,6 +137,7 @@ class MappableBuilder implements Builder { output.write('}'); + final outputId = buildStep.allowedOutputs.last; var source = DartFormatter(pageWidth: options.lineLength ?? 80).format( '// coverage:ignore-file\n' '// GENERATED CODE - DO NOT MODIFY BY HAND\n' @@ -146,7 +146,6 @@ class MappableBuilder implements Builder { '${output.toString()}\n', ); - var outputId = buildStep.inputId.changeExtension('.init.dart'); await buildStep.writeAsString(outputId, source); } } diff --git a/packages/dart_mappable_builder/lib/src/utils.dart b/packages/dart_mappable_builder/lib/src/utils.dart index c7fe574b..ea7a1e3a 100644 --- a/packages/dart_mappable_builder/lib/src/utils.dart +++ b/packages/dart_mappable_builder/lib/src/utils.dart @@ -165,3 +165,82 @@ extension ObjectReader on DartObject { return result; } } + +Map> validatedBuildExtensionsFrom( + Map? optionsMap, + Map> defaultExtensions, +) { + final extensionsOption = optionsMap?.remove('build_extensions'); + if (extensionsOption == null) { + // defaultExtensions are provided by the builder author, not the end user. + // It should be safe to skip validation. + return defaultExtensions; + } + + if (extensionsOption is! Map) { + throw ArgumentError( + 'Configured build_extensions should be a map from inputs to outputs.', + ); + } + + final result = >{}; + + for (final entry in extensionsOption.entries) { + final input = entry.key; + if (input is! String || !input.endsWith('.dart')) { + throw ArgumentError( + 'Invalid key in build_extensions option: `$input` ' + 'should be a string ending with `.dart`', + ); + } + + final output = (entry.value is List) ? entry.value as List : [entry.value]; + + for (var i = 0; i < output.length; i++) { + final o = output[i]; + if (o is! String || (i == 0 && !o.endsWith('.dart'))) { + throw ArgumentError( + 'Invalid output extension `${entry.value}`. It should be a string ' + 'or a list of strings with the first ending with `.dart`', + ); + } + } + + result[input] = output.cast().toList(); + } + + if (result.isEmpty) { + throw ArgumentError('Configured build_extensions must not be empty.'); + } + return result; +} + +String uriOfPartial(AssetId source, AssetId output) { + assert(source.package == output.package); + String sourcePath = source.path; + String outputPath = output.path; + + // Split the paths into individual segments. + List sourceSegments = sourcePath.split('/'); + List outputSegments = outputPath.split('/'); + + // Find the common prefix between source and output paths. + int commonIndex = 0; + for (int i = 0; i < sourceSegments.length && i < outputSegments.length; i++) { + if (sourceSegments[i] != outputSegments[i]) { + break; + } + commonIndex = i; + } + + // Calculate the relative path. + List relativeSegments = List.generate( + outputSegments.length - commonIndex - 2, + (_) => '..', + ); + relativeSegments.addAll(sourceSegments.sublist(commonIndex + 1)); + + // Join the segments to form the relative path. + String relativePath = relativeSegments.join('/'); + return relativePath; +} diff --git a/packages/dart_mappable_builder/test/simple_model_test.dart b/packages/dart_mappable_builder/test/simple_model_test.dart index c74d45ff..137a351d 100644 --- a/packages/dart_mappable_builder/test/simple_model_test.dart +++ b/packages/dart_mappable_builder/test/simple_model_test.dart @@ -1,3 +1,5 @@ +import 'package:build/build.dart'; +import 'package:dart_mappable_builder/src/utils.dart'; import 'package:test/test.dart'; import 'utils/test_mappable.dart'; @@ -31,5 +33,14 @@ void main() { }, ); }); + + test('uriOfPartial', () { + AssetId input = AssetId('package', 'lib/models/model.dart'); + AssetId output = AssetId('package', + 'lib/models/subfolder1/subfolder2/subfolder3/model.mapper.dart'); + expect(uriOfPartial(input, output), '../../../model.dart'); + expect(uriOfPartial(output, input), + 'subfolder1/subfolder2/subfolder3/model.mapper.dart'); + }); }); } From f741e9108d74f25dd87ac8e121619a12b7a96fa8 Mon Sep 17 00:00:00 2001 From: eymeric Date: Fri, 17 Nov 2023 10:15:22 +0100 Subject: [PATCH 2/5] add documentation --- packages/dart_mappable/doc/configuration.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/packages/dart_mappable/doc/configuration.md b/packages/dart_mappable/doc/configuration.md index 84dc4818..9c621b11 100644 --- a/packages/dart_mappable/doc/configuration.md +++ b/packages/dart_mappable/doc/configuration.md @@ -81,6 +81,26 @@ global_options: generateMethods: [decode, encode, copy, stringify, equals] ``` +### `build_extensions` + +The `build_extensions` option allows you to specify custom paths for the generated files. This is particularly useful when working with certain code generation scenarios. It takes a map where keys are paths to source files and values are lists of corresponding generated file paths. + +#### Example: + +Here is an example to write in a build.yaml file to generate the generated files in a `generated` folder: +```yaml +targets: + $default: + builders: + # only to resolve build_runner conflicts + dart_mappable_builder: + options: + build_extensions: + 'lib/{{path}}/{{file}}.dart': + - 'lib/{{path}}/generated/{{file}}.mapper.dart' + - 'lib/{{path}}/generated/{{file}}.init.dart' +``` + ---

Next: Copy-With

From f8c4930a045a1d6994773b36048e9175f76d01c6 Mon Sep 17 00:00:00 2001 From: eymeric Date: Fri, 17 Nov 2023 10:21:23 +0100 Subject: [PATCH 3/5] add test for default path --- .../dart_mappable_builder/test/simple_model_test.dart | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/dart_mappable_builder/test/simple_model_test.dart b/packages/dart_mappable_builder/test/simple_model_test.dart index 137a351d..817eda21 100644 --- a/packages/dart_mappable_builder/test/simple_model_test.dart +++ b/packages/dart_mappable_builder/test/simple_model_test.dart @@ -41,6 +41,14 @@ void main() { expect(uriOfPartial(input, output), '../../../model.dart'); expect(uriOfPartial(output, input), 'subfolder1/subfolder2/subfolder3/model.mapper.dart'); + + //add test for default path + input = AssetId('package', 'lib/model.dart'); + output = AssetId('package', 'lib/model.mapper.dart'); + expect(uriOfPartial(input, output), 'model.dart'); + expect(uriOfPartial(output, input), 'model.mapper.dart'); }); + + }); } From 60c4a38e3305bed16d3a56008f722ef58bd732d4 Mon Sep 17 00:00:00 2001 From: eymeric Date: Fri, 17 Nov 2023 10:29:04 +0100 Subject: [PATCH 4/5] improov import resilience for complexe dir tree --- .../lib/src/builders/mappable_builder.dart | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/dart_mappable_builder/lib/src/builders/mappable_builder.dart b/packages/dart_mappable_builder/lib/src/builders/mappable_builder.dart index 83c141e8..5808fae4 100644 --- a/packages/dart_mappable_builder/lib/src/builders/mappable_builder.dart +++ b/packages/dart_mappable_builder/lib/src/builders/mappable_builder.dart @@ -122,9 +122,12 @@ class MappableBuilder implements Builder { discovered.sortBy((e) => e.key.source.uri.toString()); + final outputId = buildStep.allowedOutputs.last; + output.write(writeImports( buildStep.inputId, discovered.map((e) => e.key.source.uri).toList(), + outputId.path, )); output.write('void initializeMappers() {\n'); @@ -137,7 +140,6 @@ class MappableBuilder implements Builder { output.write('}'); - final outputId = buildStep.allowedOutputs.last; var source = DartFormatter(pageWidth: options.lineLength ?? 80).format( '// coverage:ignore-file\n' '// GENERATED CODE - DO NOT MODIFY BY HAND\n' @@ -150,7 +152,7 @@ class MappableBuilder implements Builder { } } -String writeImports(AssetId input, List imports) { +String writeImports(AssetId input, List imports, String outputDirectory) { List package = [], relative = []; var prefixes = {}; @@ -193,5 +195,6 @@ String writeImports(AssetId input, List imports) { ? '${s.map((s) => "import '$s'${prefixes[s] != null ? ' as p${prefixes[s]}' : ''};").join('\n')}\n\n' : ''; - return joined(package) + joined(relative); + return joined(package) + + joined(relative.map((r) => p.join(outputDirectory, r)).toList()); } From a16a9fdcbb37507655f698ac898cd7e23fe6feb1 Mon Sep 17 00:00:00 2001 From: Kilian Schulte Date: Sun, 3 Dec 2023 19:24:17 +0100 Subject: [PATCH 5/5] fix import paths --- .../lib/src/builders/mappable_builder.dart | 21 +++++-------- .../dart_mappable_builder/lib/src/utils.dart | 30 ------------------- .../test/simple_model_test.dart | 19 ------------ 3 files changed, 8 insertions(+), 62 deletions(-) diff --git a/packages/dart_mappable_builder/lib/src/builders/mappable_builder.dart b/packages/dart_mappable_builder/lib/src/builders/mappable_builder.dart index 5808fae4..369c1991 100644 --- a/packages/dart_mappable_builder/lib/src/builders/mappable_builder.dart +++ b/packages/dart_mappable_builder/lib/src/builders/mappable_builder.dart @@ -94,13 +94,16 @@ class MappableBuilder implements Builder { var output = await Future.wait(generators.map((g) => g.generate())); final outputId = buildStep.allowedOutputs.first; + + var libraryPath = + p.relative(buildStep.inputId.path, from: p.dirname(outputId.path)); var source = DartFormatter(pageWidth: options.lineLength ?? 80).format( '// coverage:ignore-file\n' '// GENERATED CODE - DO NOT MODIFY BY HAND\n' '// ignore_for_file: type=lint\n' '// ignore_for_file: unused_element, unnecessary_cast\n' '// ignore_for_file: strict_raw_type, inference_failure_on_untyped_parameter\n\n' - 'part of \'${uriOfPartial(buildStep.inputId, outputId)}\';\n\n' + 'part of \'$libraryPath\';\n\n' '${output.join('\n\n')}\n' //, ); @@ -127,7 +130,7 @@ class MappableBuilder implements Builder { output.write(writeImports( buildStep.inputId, discovered.map((e) => e.key.source.uri).toList(), - outputId.path, + p.dirname(outputId.uri.path), )); output.write('void initializeMappers() {\n'); @@ -161,21 +164,14 @@ String writeImports(AssetId input, List imports, String outputDirectory) { for (var i = 0; i < imports.length; i++) { var import = imports[i]; if (import.isScheme('asset')) { - var relativePath = - path.relative(import.path, from: path.dirname(input.uri.path)); + var relativePath = path.relative(import.path, from: outputDirectory); relative.add(relativePath); prefixes[relativePath] = i; } else if (import.isScheme('package') && import.pathSegments.first == input.package && input.pathSegments.first == 'lib') { - var libPath = - import.replace(pathSegments: import.pathSegments.skip(1)).path; - - var inputPath = - input.uri.replace(pathSegments: input.uri.pathSegments.skip(1)).path; - - var relativePath = path.relative(libPath, from: path.dirname(inputPath)); + var relativePath = path.relative(import.path, from: outputDirectory); relative.add(relativePath); prefixes[relativePath] = i; @@ -195,6 +191,5 @@ String writeImports(AssetId input, List imports, String outputDirectory) { ? '${s.map((s) => "import '$s'${prefixes[s] != null ? ' as p${prefixes[s]}' : ''};").join('\n')}\n\n' : ''; - return joined(package) + - joined(relative.map((r) => p.join(outputDirectory, r)).toList()); + return joined(package) + joined(relative); } diff --git a/packages/dart_mappable_builder/lib/src/utils.dart b/packages/dart_mappable_builder/lib/src/utils.dart index ea7a1e3a..537e9204 100644 --- a/packages/dart_mappable_builder/lib/src/utils.dart +++ b/packages/dart_mappable_builder/lib/src/utils.dart @@ -214,33 +214,3 @@ Map> validatedBuildExtensionsFrom( } return result; } - -String uriOfPartial(AssetId source, AssetId output) { - assert(source.package == output.package); - String sourcePath = source.path; - String outputPath = output.path; - - // Split the paths into individual segments. - List sourceSegments = sourcePath.split('/'); - List outputSegments = outputPath.split('/'); - - // Find the common prefix between source and output paths. - int commonIndex = 0; - for (int i = 0; i < sourceSegments.length && i < outputSegments.length; i++) { - if (sourceSegments[i] != outputSegments[i]) { - break; - } - commonIndex = i; - } - - // Calculate the relative path. - List relativeSegments = List.generate( - outputSegments.length - commonIndex - 2, - (_) => '..', - ); - relativeSegments.addAll(sourceSegments.sublist(commonIndex + 1)); - - // Join the segments to form the relative path. - String relativePath = relativeSegments.join('/'); - return relativePath; -} diff --git a/packages/dart_mappable_builder/test/simple_model_test.dart b/packages/dart_mappable_builder/test/simple_model_test.dart index 817eda21..c74d45ff 100644 --- a/packages/dart_mappable_builder/test/simple_model_test.dart +++ b/packages/dart_mappable_builder/test/simple_model_test.dart @@ -1,5 +1,3 @@ -import 'package:build/build.dart'; -import 'package:dart_mappable_builder/src/utils.dart'; import 'package:test/test.dart'; import 'utils/test_mappable.dart'; @@ -33,22 +31,5 @@ void main() { }, ); }); - - test('uriOfPartial', () { - AssetId input = AssetId('package', 'lib/models/model.dart'); - AssetId output = AssetId('package', - 'lib/models/subfolder1/subfolder2/subfolder3/model.mapper.dart'); - expect(uriOfPartial(input, output), '../../../model.dart'); - expect(uriOfPartial(output, input), - 'subfolder1/subfolder2/subfolder3/model.mapper.dart'); - - //add test for default path - input = AssetId('package', 'lib/model.dart'); - output = AssetId('package', 'lib/model.mapper.dart'); - expect(uriOfPartial(input, output), 'model.dart'); - expect(uriOfPartial(output, input), 'model.mapper.dart'); - }); - - }); }