我正在开发Flutter应用程序,并且想编写一个构建脚本,以将某种原始文件(以CSV格式)转换为格式化的JSON文件,以作为Flutter资产包括在内。
通过使用json_serializable
和jaguar_serializer
之类的库,我了解了build_runner
,因此看来编写自己的Builder
并通过build_runner
调用它是一种明智的方式。
由于编写我们自己的构建脚本的资源非常有限,因此我首先修改了here的示例。但是当尝试更改输入和输出文件的路径时,我陷入了困境:运行flutter pub pub run build_runner build
时,事实证明Dart只在[project_dir]/web
目录中搜索匹配的文件,并且只允许我执行以下操作:将文件写入此web
目录。所以这段代码
buildStep.writeAsString(new AssetId(buildStep.inputId.package, 'assets/resources/foo.json'), '[]');
将产生以下异常:
UnexpectedOutputException: myapp|assets/resources/foo.json
Expected only: {myapp|web/.json}
[SEVERE] Failed after 24.4s
json_serializable
和jaguar_serializer
可以自由在任何地方生成代码的事实似乎表明我的配置有问题。但是我在代码中web
的任何地方都找不到build.yaml
这个东西,所以这真令人困惑。
FWIW,这是我的builder.yaml
文件的内容:
builders:
jsonBuilder:
import: "package:myapp/builder.dart"
builder_factories: ["jsonFileBuilder"]
build_extensions: {"source.csv": [".json"]}
build_to: source
auto_apply: root_package
这是builder.dart
文件
import 'dart:async';
import 'package:build/build.dart';
Builder jsonFileBuilder(BuilderOptions options) => new JsonFileBuilder();
class JsonFileBuilder implements Builder {
@override
Future build(BuildStep buildStep) async {
await buildStep.writeAsString(
new AssetId(buildStep.inputId.package, 'assets/resources/foo.json'),
'hello');
}
@override
final buildExtensions = const {
'source.csv': const ['.json']
};
}
谢谢!
答案 0 :(得分:2)
好吧,看来问题实际上是双重的。
我的问题是,如果我将源CSV文件放在某个任意目录(例如offline
)下,由于找不到匹配的文件,我的构建脚本将无法运行。但是,当我将文件放在web
下时(如示例中所示),脚本将被调用。这给我一种幻觉,即文件扫描仅限于web/
。事实是,这里有hard-coded file/directory whitelist的列表:
const List<String> _defaultRootPackageWhitelist = const [
'benchmark/**',
'bin/**',
'example/**',
'lib/**',
'test/**',
'tool/**',
'web/**',
'pubspec.yaml',
'pubspec.lock',
];
如果将CSV文件放在lib/
下,也会调用我的构建脚本。
要将未列入白名单的文件添加到列表中,我必须将以下部分添加到build.yaml
中。
targets:
$default:
sources:
# Need to replicate the default whitelist here or other build will break:
- "benchmark/**"
- "bin/**"
- "example/**"
- "lib/**"
- "test/**"
- "tool/**"
- "web/**"
- "pubspec.yaml"
- "pubspec.lock"
# My new source
- "offline/source.csv"
我的问题是我无法写入任意目录,构建系统不断抱怨我只能写入web/
。因此,我认为文件写入已锁定为web/
。同样,这并非完全正确,因为文件写入实际上已锁定到输入文件的目录。这意味着,我只有在构建运行器成功识别此目录中的文件之后,才能写入offline/
。
文件写入权限的检查由this method in build_step_impl.dart
控制:
void _checkOutput(AssetId id) {
if (!_expectedOutputs.contains(id)) {
throw new UnexpectedOutputException(id, expected: _expectedOutputs);
}
}
此_expectedOutputs
是我的构建器类的buildExtensions
映射的平坦值。因此,如果我真的想写assets/resources/foo.json
,我必须写:
@override
final buildExtensions = const {
'source.csv': const ['/../../assets/resources/foo.json']
};
必须对build.yaml
进行类似的调整:
build_extensions: {".csv": ["/../../assets/resources/foo.json"]}
这基本上解决了我的大多数问题,但仍然存在一些问题:
首先,我打算编写一个构建脚本,该脚本可以读取一个文件并写入多个文件。文件的数量及其文件名取决于源文件的内容。给定检查代码,这似乎不是受支持的功能。不是障碍,但有点不方便。
第二个关于输出文件路径。您可以看到我的源文件位于[projectDir]/offline
中,并且我想写入[projectDir]/assets
,但是输出文件名读取/../../assets/...
,这是两个级别。这是因为原始文件名(不带扩展名)位于输出路径之前,这就是为什么在生成Dart代码的构建器配置中会看到.g.dart
的原因。我需要编写/../../assets
,以便文件的完整输出路径变为[projectDir]/offline/source/../../assets
,以使其解析为所需的输出文件路径。但是默认行为给我一种印象,即构建系统不希望我的脚本将脚本写入当前文件目录之外的任何位置,并且我正在做的事情是对系统的黑客攻击或滥用。