Skip to content

Commit 73ba4a7

Browse files
generator: normalize out dir before comparing it (#32)
1 parent 1fcb507 commit 73ba4a7

File tree

6 files changed

+239
-42
lines changed

6 files changed

+239
-42
lines changed
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import 'package:build/build.dart';
2+
import 'package:path/path.dart' as path;
3+
4+
import 'config.dart';
5+
6+
class BuilderDirs {
7+
/// The normalized path to the source code root directory.
8+
final String root;
9+
10+
/// The normalized path to the output directory for generated code.
11+
final String out;
12+
13+
BuilderDirs._(this.root, this.out);
14+
15+
factory BuilderDirs(BuildStep buildStep, Config config) {
16+
// Paths from Config are supplied by the user and may contain duplicate
17+
// slashes or be empty: normalize all paths to not return duplicate or
18+
// trailing slashes to ensure they can be compared via strings.
19+
final root = path.normalize(path.dirname(buildStep.inputId.path));
20+
final String out;
21+
if (root.endsWith('test')) {
22+
out = path.normalize('$root/${config.outDirTest}');
23+
} else if (root.endsWith('lib')) {
24+
out = path.normalize('$root/${config.outDirLib}');
25+
} else {
26+
throw ArgumentError('Is not lib or test directory: "$root"');
27+
}
28+
return BuilderDirs._(root, out);
29+
}
30+
}

generator/lib/src/code_builder.dart

Lines changed: 40 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import 'dart:convert';
44

55
import 'package:build/build.dart';
66
import 'package:glob/glob.dart';
7+
import 'package:objectbox_generator/src/builder_dirs.dart';
78
import 'package:path/path.dart' as path;
89
import 'package:objectbox/internal.dart';
910
import 'package:dart_style/dart_style.dart';
@@ -27,25 +28,14 @@ class CodeBuilder extends Builder {
2728
r'$test$': [path.join(_config.outDirTest, _config.codeFile)]
2829
};
2930

30-
String _dir(BuildStep buildStep) => path.dirname(buildStep.inputId.path);
31-
32-
String _outDir(BuildStep buildStep) {
33-
var dir = _dir(buildStep);
34-
if (dir.endsWith('test')) {
35-
return dir + '/' + _config.outDirTest;
36-
} else if (dir.endsWith('lib')) {
37-
return dir + '/' + _config.outDirLib;
38-
} else {
39-
throw Exception('Unrecognized path being generated: $dir');
40-
}
41-
}
42-
4331
@override
4432
FutureOr<void> build(BuildStep buildStep) async {
33+
final builderDirs = BuilderDirs(buildStep, _config);
34+
4535
// build() will be called only twice, once for the `lib` directory and once for the `test` directory
4636
// map from file name to a 'json' representation of entities
4737
final files = <String, List<dynamic>>{};
48-
final glob = Glob(_dir(buildStep) + '/**' + EntityResolver.suffix);
38+
final glob = Glob('${builderDirs.root}/**${EntityResolver.suffix}');
4939
await for (final input in buildStep.findAssets(glob)) {
5040
files[input.path] = json.decode(await buildStep.readAsString(input))!;
5141
}
@@ -64,26 +54,27 @@ class CodeBuilder extends Builder {
6454
log.info('Found ${entities.length} entities in: ${files.keys}');
6555

6656
// update the model JSON with the read entities
67-
final model = await updateModel(entities, buildStep);
57+
final model = await updateModel(entities, buildStep, builderDirs);
6858

6959
Pubspec? pubspec;
7060
try {
71-
final pubspecFile = File(path.join(_dir(buildStep), '../pubspec.yaml'));
61+
final pubspecFile = File(path.join(builderDirs.root, '../pubspec.yaml'));
7262
pubspec = Pubspec.parse(pubspecFile.readAsStringSync());
7363
} catch (e) {
7464
log.info("Couldn't load pubspec.yaml: $e");
7565
}
7666

7767
// generate binding code
78-
updateCode(model, files.keys.toList(growable: false), buildStep, pubspec);
68+
updateCode(model, files.keys.toList(growable: false), buildStep,
69+
builderDirs, pubspec);
7970
}
8071

81-
Future<ModelInfo> updateModel(
82-
List<ModelEntity> entities, BuildStep buildStep) async {
72+
Future<ModelInfo> updateModel(List<ModelEntity> entities, BuildStep buildStep,
73+
BuilderDirs builderDirs) async {
8374
// load an existing model or initialize a new one
8475
ModelInfo model;
8576
final jsonId = AssetId(
86-
buildStep.inputId.package, _outDir(buildStep) + '/' + _config.jsonFile);
77+
buildStep.inputId.package, '${builderDirs.out}/${_config.jsonFile}');
8778
if (await buildStep.canRead(jsonId)) {
8879
log.info('Using model: ${jsonId.path}');
8980
model =
@@ -106,44 +97,54 @@ class CodeBuilder extends Builder {
10697
return model;
10798
}
10899

109-
void updateCode(ModelInfo model, List<String> infoFiles, BuildStep buildStep,
110-
Pubspec? pubspec) async {
111-
// If output directory is not package root directory,
112-
// need to prefix imports with as many '../' to be relative from root.
113-
final rootPath = _dir(buildStep);
114-
final outPath = _outDir(buildStep);
115-
final rootDir = Directory(rootPath).absolute;
116-
var outDir = Directory(outPath).absolute;
117-
var prefix = '';
100+
/// Returns a prefix for imports if the output directory is not the
101+
/// package root directory.
102+
///
103+
/// Returns the empty string if the output directory is the root directory,
104+
/// otherwise adds as many '../' as necessary to be relative from root.
105+
///
106+
/// Returns null if the root directory was not found by walking up from the
107+
/// output directory.
108+
static String? getPrefixFor(BuilderDirs builderDirs) {
109+
// Note: comparing path strings below so paths should be normalized (they
110+
// are by BuilderDirs).
111+
final rootDir = Directory(builderDirs.root).absolute;
112+
var outDir = Directory(builderDirs.out).absolute;
118113

119114
if (!outDir.path.startsWith(rootDir.path)) {
120115
throw InvalidGenerationSourceError(
121116
'configured output_dir ${outDir.path} is not a '
122117
'subdirectory of the source directory ${rootDir.path}');
123118
}
124119

120+
var prefix = '';
125121
while (outDir.path != rootDir.path) {
126122
final parent = outDir.parent;
127123
if (parent.path == outDir.path) {
128-
log.warning(
129-
'Failed to find package root from output directory, generated imports might be incorrect.');
130-
prefix = '';
131-
break; // Reached top-most directory, stop searching.
124+
return null; // Reached top-most directory, stop searching.
132125
}
133126
outDir = parent;
134127
prefix += '../';
135128
}
136-
if (prefix.isNotEmpty) {
129+
return prefix;
130+
}
131+
132+
void updateCode(ModelInfo model, List<String> infoFiles, BuildStep buildStep,
133+
BuilderDirs builderDirs, Pubspec? pubspec) async {
134+
var prefix = getPrefixFor(builderDirs);
135+
if (prefix == null) {
136+
log.warning(
137+
'Failed to find package root from output directory, generated imports might be incorrect (rootDir="${builderDirs.root}", outDir="${builderDirs.out}")');
138+
} else if (prefix.isNotEmpty) {
137139
log.info(
138-
'Output directory not in package root, adding prefix to imports: ' +
139-
prefix);
140+
'Output directory not in package root, adding prefix to imports: $prefix');
140141
}
141142

142143
// transform '/lib/path/entity.objectbox.info' to 'path/entity.dart'
143144
final imports = infoFiles
144145
.map((file) => file
145146
.replaceFirst(EntityResolver.suffix, '.dart')
146-
.replaceFirst(rootPath + '/', prefix))
147+
.replaceFirst('${builderDirs.root}/', prefix ?? ''))
147148
.toList();
148149

149150
var code = CodeChunks.objectboxDart(model, imports, pubspec);
@@ -152,8 +153,8 @@ class CodeBuilder extends Builder {
152153
code = DartFormatter().format(code);
153154
} finally {
154155
// Write the code even after a formatter error so it's easier to debug.
155-
final codeId =
156-
AssetId(buildStep.inputId.package, outPath + '/' + _config.codeFile);
156+
final codeId = AssetId(
157+
buildStep.inputId.package, '${builderDirs.out}/${_config.codeFile}');
157158
log.info('Generating code: ${codeId.path}');
158159
await buildStep.writeAsString(codeId, code);
159160
}

generator/lib/src/config.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class Config {
2222
final String outDirLib;
2323
final String outDirTest;
2424

25-
Config._(
25+
Config(
2626
{String? jsonFile,
2727
String? codeFile,
2828
String? outDirLib,
@@ -48,9 +48,9 @@ class Config {
4848
outDirLib = outDirTest = outDirYaml as String?;
4949
}
5050

51-
return Config._(outDirLib: outDirLib, outDirTest: outDirTest);
51+
return Config(outDirLib: outDirLib, outDirTest: outDirTest);
5252
}
5353
}
54-
return Config._();
54+
return Config();
5555
}
5656
}

generator/pubspec.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ dependencies:
2020
yaml: ^3.0.0
2121

2222
dev_dependencies:
23+
test: ^1.16.5
2324
lints: ^2.0.1
2425

2526
# NOTE: remove before publishing
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
import 'dart:async';
2+
import 'dart:convert';
3+
4+
import 'package:objectbox_generator/src/builder_dirs.dart';
5+
import 'package:path/path.dart' as path;
6+
import 'package:build/build.dart';
7+
import 'package:build/src/builder/build_step_impl.dart';
8+
import 'package:crypto/src/digest.dart';
9+
import 'package:glob/glob.dart';
10+
import 'package:objectbox_generator/src/code_builder.dart';
11+
import 'package:objectbox_generator/src/config.dart';
12+
import 'package:source_gen/source_gen.dart';
13+
import 'package:test/test.dart';
14+
15+
void main() {
16+
var reader = StubAssetReader();
17+
var writer = StubAssetWriter();
18+
final resourceManager = ResourceManager();
19+
// Default directory structure: sources inside lib folder.
20+
final testBuildStep = BuildStepImpl(
21+
AssetId("objectbox_generator_test", "lib/\$lib\$"),
22+
[],
23+
reader,
24+
writer,
25+
null,
26+
resourceManager);
27+
28+
group('getRootDir and getOutDir', () {
29+
test('lib', () {
30+
final builderDirs = BuilderDirs(testBuildStep, Config());
31+
expect(builderDirs.root, equals('lib'));
32+
expect(builderDirs.out, equals('lib'));
33+
});
34+
35+
test('test', () {
36+
final testBuildStepTest = BuildStepImpl(
37+
AssetId("objectbox_generator_test", "test/\$test\$"),
38+
[],
39+
reader,
40+
writer,
41+
null,
42+
resourceManager);
43+
final builderDirs = BuilderDirs(testBuildStepTest, Config());
44+
expect(builderDirs.root, equals('test'));
45+
expect(builderDirs.out, equals('test'));
46+
});
47+
48+
test('not supported', () {
49+
final testBuildStepNotSupported = BuildStepImpl(
50+
AssetId("objectbox_generator_test", "custom/\$custom\$"),
51+
[],
52+
reader,
53+
writer,
54+
null,
55+
resourceManager);
56+
expect(
57+
() => BuilderDirs(testBuildStepNotSupported, Config()),
58+
throwsA(predicate((e) =>
59+
e is ArgumentError &&
60+
e.message == 'Is not lib or test directory: "custom"')));
61+
});
62+
63+
test('out dir with redundant slash', () {
64+
final builderDirs = BuilderDirs(testBuildStep, Config(outDirLib: '/'));
65+
expect(builderDirs.root, equals('lib'));
66+
expect(builderDirs.out, equals(path.normalize('lib')));
67+
});
68+
69+
test('out dir not in root dir', () {
70+
final builderDirs =
71+
BuilderDirs(testBuildStep, Config(outDirLib: '../sibling'));
72+
expect(builderDirs.root, equals('lib'));
73+
expect(builderDirs.out, equals(path.normalize('sibling')));
74+
});
75+
76+
test('out dir below root dir', () {
77+
final builderDirs =
78+
BuilderDirs(testBuildStep, Config(outDirLib: 'below/root'));
79+
expect(builderDirs.root, equals('lib'));
80+
expect(builderDirs.out, equals(path.normalize('lib/below/root')));
81+
});
82+
});
83+
84+
group('getPrefixFor', () {
85+
test('out dir is root dir', () {
86+
expect(CodeBuilder.getPrefixFor(BuilderDirs(testBuildStep, Config())),
87+
equals(''));
88+
});
89+
90+
test('out dir redundant slash', () {
91+
expect(
92+
CodeBuilder.getPrefixFor(
93+
BuilderDirs(testBuildStep, Config(outDirLib: '/'))),
94+
equals(''));
95+
expect(
96+
CodeBuilder.getPrefixFor(
97+
BuilderDirs(testBuildStep, Config(outDirLib: '//below/'))),
98+
equals('../'));
99+
});
100+
101+
test('out dir not in root dir', () {
102+
expect(
103+
() => CodeBuilder.getPrefixFor(
104+
BuilderDirs(testBuildStep, Config(outDirLib: '../sibling'))),
105+
throwsA(predicate((e) =>
106+
e is InvalidGenerationSourceError &&
107+
e.message
108+
.contains("is not a subdirectory of the source directory"))));
109+
110+
expect(
111+
() => CodeBuilder.getPrefixFor(
112+
BuilderDirs(testBuildStep, Config(outDirLib: '../../above'))),
113+
throwsA(predicate((e) =>
114+
e is InvalidGenerationSourceError &&
115+
e.message
116+
.contains("is not a subdirectory of the source directory"))));
117+
});
118+
119+
test('out dir below root dir', () {
120+
expect(
121+
CodeBuilder.getPrefixFor(
122+
BuilderDirs(testBuildStep, Config(outDirLib: 'below'))),
123+
equals('../'));
124+
expect(
125+
CodeBuilder.getPrefixFor(
126+
BuilderDirs(testBuildStep, Config(outDirLib: 'below/lower'))),
127+
equals('../../'));
128+
});
129+
});
130+
}
131+
132+
/// A no-op implementation of [AssetReader].
133+
class StubAssetReader extends AssetReader implements MultiPackageAssetReader {
134+
StubAssetReader();
135+
136+
@override
137+
Future<bool> canRead(AssetId id) => Future.value(false);
138+
139+
@override
140+
Future<List<int>> readAsBytes(AssetId id) => Future.value([]);
141+
142+
@override
143+
Future<String> readAsString(AssetId id, {Encoding encoding = utf8}) =>
144+
Future.value('');
145+
146+
@override
147+
Stream<AssetId> findAssets(Glob glob, {String? package}) =>
148+
const Stream<Never>.empty();
149+
150+
@override
151+
Future<Digest> digest(AssetId id) => Future.value(Digest([1, 2, 3]));
152+
}
153+
154+
/// A no-op implementation of [AssetWriter].
155+
class StubAssetWriter implements AssetWriter {
156+
const StubAssetWriter();
157+
158+
@override
159+
Future writeAsBytes(_, __) => Future.value(null);
160+
161+
@override
162+
Future writeAsString(_, __, {Encoding encoding = utf8}) => Future.value(null);
163+
}

objectbox/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
final query = box.query().order(Person_.name).build();
99
```
1010
* Allow `analyzer` with major version 5. #487
11+
* Generator not longer warns that it can not find the package source root if the output directory is
12+
the package root directory.
1113

1214
## 1.6.2 (2022-08-24)
1315

0 commit comments

Comments
 (0)