Skip to content

Commit 3c34e8c

Browse files
Merge pull request #341 from objectbox/312-generator-config
Add an option to configure generator's output_dir
2 parents 9bf142d + 11d4165 commit 3c34e8c

File tree

11 files changed

+231
-16
lines changed

11 files changed

+231
-16
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# start with an empty project, without a objectbox-model.json
2+
objectbox-model.json
3+
objectbox.*
4+
testdata
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import 'dart:io';
2+
import 'dart:typed_data';
3+
4+
import 'package:test/test.dart';
5+
6+
import 'lib/lib.dart';
7+
import 'lib/custom/objectbox.g.dart';
8+
import '../test_env.dart';
9+
import '../common.dart';
10+
11+
void main() {
12+
late TestEnv<A> env;
13+
final jsonModel = readModelJson('lib/custom');
14+
final defs = getObjectBoxModel();
15+
final model = defs.model;
16+
17+
setUp(() {
18+
env = TestEnv<A>(defs);
19+
});
20+
21+
tearDown(() {
22+
env.close();
23+
});
24+
25+
commonModelTests(defs, jsonModel);
26+
27+
test('project must be generated properly', () {
28+
expect(TestEnv.dir.existsSync(), true);
29+
expect(File('lib/custom/objectbox.g.dart').existsSync(), true);
30+
expect(File('lib/custom/objectbox-model.json').existsSync(), true);
31+
});
32+
33+
// Very simple tests to ensure imports and generated code is correct.
34+
35+
test('types', () {
36+
expect(property(model, 'A.text').type, OBXPropertyType.String);
37+
});
38+
39+
test('db-ops-A', () {
40+
final box = env.store.box<A>();
41+
expect(box.count(), 0);
42+
43+
final inserted = A();
44+
box.put(inserted);
45+
expect(inserted.id, 1);
46+
box.get(inserted.id!)!;
47+
});
48+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
This file just exists so its folder is created by git.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import 'dart:typed_data';
2+
3+
import 'package:objectbox/objectbox.dart';
4+
5+
import 'custom/objectbox.g.dart';
6+
7+
@Entity()
8+
class A {
9+
int? id;
10+
String? text;
11+
12+
A();
13+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
name: objectbox_generator_test
2+
3+
environment:
4+
sdk: ">=2.12.0 <3.0.0"
5+
6+
dependencies:
7+
objectbox: any
8+
9+
dev_dependencies:
10+
objectbox_generator: any
11+
test: any
12+
build_runner: any
13+
build_test: any
14+
io: any
15+
path: any
16+
17+
dependency_overrides:
18+
objectbox:
19+
path: ../../../objectbox
20+
objectbox_generator:
21+
path: ../../
22+
23+
24+
objectbox:
25+
output_dir: custom
26+
# output_dir:
27+
# lib: custom
28+
# test: other
29+
Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
/// This package provides code generation for ObjectBox in Dart/Flutter.
2+
23
import 'package:build/build.dart';
3-
import 'src/entity_resolver.dart';
4+
45
import 'src/code_builder.dart';
6+
import 'src/config.dart';
7+
import 'src/entity_resolver.dart';
8+
9+
final _config = Config.readFromPubspec();
510

611
/// Finds all classes annotated with @Entity annotation and creates intermediate files for the generator.
712
Builder entityResolverFactory(BuilderOptions options) => EntityResolver();
813

914
/// Writes objectbox_model.dart and objectbox-model.json from the prepared .objectbox.info files found in the repo.
10-
Builder codeGeneratorFactory(BuilderOptions options) => CodeBuilder();
15+
Builder codeGeneratorFactory(BuilderOptions options) => CodeBuilder(_config);

generator/lib/src/code_builder.dart

Lines changed: 58 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,29 +10,42 @@ import 'package:dart_style/dart_style.dart';
1010
import 'package:source_gen/source_gen.dart';
1111
import 'package:pubspec_parse/pubspec_parse.dart';
1212

13+
import 'config.dart';
1314
import 'entity_resolver.dart';
1415
import 'code_chunks.dart';
1516

1617
/// CodeBuilder collects all '.objectbox.info' files created by EntityResolver and generates objectbox-model.json and
1718
/// objectbox_model.dart
1819
class CodeBuilder extends Builder {
19-
static final jsonFile = 'objectbox-model.json';
20-
static final codeFile = 'objectbox.g.dart';
20+
final Config _config;
2121

22-
@override
23-
final buildExtensions = {r'$lib$': _outputs, r'$test$': _outputs};
24-
25-
// we can't write `jsonFile` as part of the output because we want it persisted, not removed before each generation
26-
static final _outputs = [codeFile];
22+
CodeBuilder(this._config);
2723

28-
String dir(BuildStep buildStep) => path.dirname(buildStep.inputId.path);
24+
@override
25+
late final buildExtensions = {
26+
r'$lib$': [path.join(_config.outDirLib, _config.codeFile)],
27+
r'$test$': [path.join(_config.outDirTest, _config.codeFile)]
28+
};
29+
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+
}
2942

3043
@override
3144
FutureOr<void> build(BuildStep buildStep) async {
3245
// build() will be called only twice, once for the `lib` directory and once for the `test` directory
3346
// map from file name to a 'json' representation of entities
3447
final files = <String, List<dynamic>>{};
35-
final glob = Glob(dir(buildStep) + '/**' + EntityResolver.suffix);
48+
final glob = Glob(_dir(buildStep) + '/**' + EntityResolver.suffix);
3649
await for (final input in buildStep.findAssets(glob)) {
3750
files[input.path] = json.decode(await buildStep.readAsString(input))!;
3851
}
@@ -55,7 +68,7 @@ class CodeBuilder extends Builder {
5568

5669
Pubspec? pubspec;
5770
try {
58-
final pubspecFile = File(path.join(dir(buildStep), '../pubspec.yaml'));
71+
final pubspecFile = File(path.join(_dir(buildStep), '../pubspec.yaml'));
5972
pubspec = Pubspec.parse(pubspecFile.readAsStringSync());
6073
} catch (e) {
6174
log.info("Couldn't load pubspec.yaml: $e");
@@ -69,8 +82,8 @@ class CodeBuilder extends Builder {
6982
List<ModelEntity> entities, BuildStep buildStep) async {
7083
// load an existing model or initialize a new one
7184
ModelInfo model;
72-
final jsonId =
73-
AssetId(buildStep.inputId.package, dir(buildStep) + '/' + jsonFile);
85+
final jsonId = AssetId(
86+
buildStep.inputId.package, _outDir(buildStep) + '/' + _config.jsonFile);
7487
if (await buildStep.canRead(jsonId)) {
7588
log.info('Using model: ${jsonId.path}');
7689
model =
@@ -95,11 +108,42 @@ class CodeBuilder extends Builder {
95108

96109
void updateCode(ModelInfo model, List<String> infoFiles, BuildStep buildStep,
97110
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 = '';
118+
119+
if (!outDir.path.startsWith(rootDir.path)) {
120+
throw InvalidGenerationSourceError(
121+
'configured output_dir ${outDir.path} is not a '
122+
'subdirectory of the source directory ${rootDir.path}');
123+
}
124+
125+
while (outDir.path != rootDir.path) {
126+
final parent = outDir.parent;
127+
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.
132+
}
133+
outDir = parent;
134+
prefix += '../';
135+
}
136+
if (prefix.isNotEmpty) {
137+
log.info(
138+
'Output directory not in package root, adding prefix to imports: ' +
139+
prefix);
140+
}
141+
98142
// transform '/lib/path/entity.objectbox.info' to 'path/entity.dart'
99143
final imports = infoFiles
100144
.map((file) => file
101145
.replaceFirst(EntityResolver.suffix, '.dart')
102-
.replaceFirst(dir(buildStep) + '/', ''))
146+
.replaceFirst(rootPath + '/', prefix))
103147
.toList();
104148

105149
var code = CodeChunks.objectboxDart(model, imports, pubspec);
@@ -109,7 +153,7 @@ class CodeBuilder extends Builder {
109153
} finally {
110154
// Write the code even after a formatter error so it's easier to debug.
111155
final codeId =
112-
AssetId(buildStep.inputId.package, dir(buildStep) + '/' + codeFile);
156+
AssetId(buildStep.inputId.package, outPath + '/' + _config.codeFile);
113157
log.info('Generating code: ${codeId.path}');
114158
await buildStep.writeAsString(codeId, code);
115159
}

generator/lib/src/config.dart

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import 'dart:io';
2+
3+
import 'package:yaml/yaml.dart';
4+
5+
const _pubspecFile = 'pubspec.yaml';
6+
const _pubspecKey = 'objectbox';
7+
8+
/// Config reads and holds configuration for the code generator.
9+
///
10+
/// Expected format in pubspec.yaml:
11+
/// ```
12+
/// objectbox:
13+
/// output_dir: custom
14+
/// # Or optionally specify lib and test folder separately.
15+
/// # output_dir:
16+
/// # lib: custom
17+
/// # test: other
18+
/// ```
19+
class Config {
20+
final String jsonFile;
21+
final String codeFile;
22+
final String outDirLib;
23+
final String outDirTest;
24+
25+
Config._(
26+
{String? jsonFile,
27+
String? codeFile,
28+
String? outDirLib,
29+
String? outDirTest})
30+
: jsonFile = jsonFile ?? 'objectbox-model.json',
31+
codeFile = codeFile ?? 'objectbox.g.dart',
32+
outDirLib = outDirLib ?? '',
33+
outDirTest = outDirTest ?? '';
34+
35+
factory Config.readFromPubspec() {
36+
final file = File(_pubspecFile);
37+
if (file.existsSync()) {
38+
final yaml = loadYaml(file.readAsStringSync())[_pubspecKey] as YamlMap?;
39+
if (yaml != null) {
40+
late final String? outDirLib;
41+
late final String? outDirTest;
42+
final outDirYaml = yaml['output_dir'];
43+
44+
if (outDirYaml is YamlMap) {
45+
outDirLib = outDirYaml['lib'];
46+
outDirTest = outDirYaml['test'];
47+
} else {
48+
outDirLib = outDirTest = outDirYaml as String?;
49+
}
50+
51+
return Config._(outDirLib: outDirLib, outDirTest: outDirTest);
52+
}
53+
}
54+
return Config._();
55+
}
56+
}

generator/pubspec.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ dependencies:
1717
path: ^1.8.0
1818
source_gen: ^1.0.0
1919
pubspec_parse: ^1.0.0
20+
yaml: ^3.0.0
2021

2122
# NOTE: remove before publishing
2223
dependency_overrides:

objectbox/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
## latest
22

3+
* Add an option to change code-generator's `output_dir` in `pubspec.yaml`. #341
4+
35
## 1.3.0 (2021-11-22)
46

57
* Support annotating a single property with `@Unique(onConflict: ConflictStrategy.replace)` to

0 commit comments

Comments
 (0)