dart-lang / build

A build system for Dart written in Dart
https://pub.dev/packages/build
BSD 3-Clause "New" or "Revised" License
791 stars 211 forks source link

More than 1 target makes it easy to create duplicate outputs #2016

Open matanlurey opened 5 years ago

matanlurey commented 5 years ago

I'm trying to consume my hobby package hquplink/swlegion:

... in another package. I wrote the following pubspec.yaml:

name: simulator

homepage: https://github.com/hquplink/simulator
description: "Simulation Explorer for Star Wars: Legion"
authors:
  - Matan Lurey <matan@hquplink.com>

# This is an application, and not for publishing to `pub`.
# version:

environment:
  sdk: ">=2.0.0 <3.0.0"

dependencies:
  swlegion:
    git:
      url: git@github.com:hquplink/swlegion.git
      ref: v0.4

dev_dependencies:
  build_runner:
  build_web_compilers:
  test: ^1.5.1

... and after pub run build_runner serve, I get:

[INFO] Generating build script completed, took 325ms
[INFO] Creating build script snapshot... completed, took 10.8s
[INFO] Setting up file watchers completed, took 24ms
[INFO] Waiting for all file watchers to be ready completed, took 198ms
[SEVERE] Conflicting outputs
Both build_modules|module_library and build_modules|module_library may output package:swlegion/src/database/weapons/ax_20_blaster_cannon.module.library. Potential outputs must be unique across all builders. See https://github.com/dart-lang/build/blob/master/docs/faq.md#why-do-builders-need-unique-outputs

Here is an example of my .dart_tool/build/build.dart output:

// ignore_for_file: directives_ordering

import 'package:build_runner_core/build_runner_core.dart' as _i1;
import 'package:build_modules/builders.dart' as _i2;
import 'package:build_web_compilers/builders.dart' as _i3;
import 'package:build_config/build_config.dart' as _i4;
import 'package:build/build.dart' as _i5;
import 'dart:isolate' as _i6;
import 'package:build_runner/build_runner.dart' as _i7;

final _builders = <_i1.BuilderApplication>[
  _i1.apply('build_modules|module_library', [_i2.moduleLibraryBuilder],
      _i1.toAllPackages(),
      isOptional: true,
      hideOutput: true,
      appliesBuilders: ['build_modules|module_cleanup']),
  _i1.apply(
      'build_modules|vm',
      [
        _i2.metaModuleBuilderFactoryForPlatform('vm'),
        _i2.metaModuleCleanBuilderFactoryForPlatform('vm'),
        _i2.moduleBuilderFactoryForPlatform('vm')
      ],
      _i1.toNoneByDefault(),
      isOptional: true,
      hideOutput: true,
      appliesBuilders: ['build_modules|module_cleanup']),
  _i1.apply(
      'build_modules|dart2js',
      [
        _i2.metaModuleBuilderFactoryForPlatform('dart2js'),
        _i2.metaModuleCleanBuilderFactoryForPlatform('dart2js'),
        _i2.moduleBuilderFactoryForPlatform('dart2js')
      ],
      _i1.toNoneByDefault(),
      isOptional: true,
      hideOutput: true,
      appliesBuilders: ['build_modules|module_cleanup']),
  _i1.apply(
      'build_modules|dartdevc',
      [
        _i2.metaModuleBuilderFactoryForPlatform('dartdevc'),
        _i2.metaModuleCleanBuilderFactoryForPlatform('dartdevc'),
        _i2.moduleBuilderFactoryForPlatform('dartdevc'),
        _i2.unlinkedSummaryBuilderForPlatform('dartdevc'),
        _i2.linkedSummaryBuilderForPlatform('dartdevc')
      ],
      _i1.toNoneByDefault(),
      isOptional: true,
      hideOutput: true,
      appliesBuilders: ['build_modules|module_cleanup']),
  _i1.apply(
      'build_web_compilers|ddc', [_i3.devCompilerBuilder], _i1.toAllPackages(),
      isOptional: true,
      hideOutput: true,
      appliesBuilders: [
        'build_web_compilers|dart_source_cleanup',
        'build_modules|dartdevc',
        'build_modules|dart2js'
      ]),
  _i1.apply('build_web_compilers|entrypoint', [_i3.webEntrypointBuilder],
      _i1.toRoot(),
      hideOutput: true,
      defaultGenerateFor: const _i4.InputSet(include: [
        'web/**',
        'test/**_test.dart',
        'example/**',
        'benchmark/**'
      ], exclude: [
        'test/**.node_test.dart',
        'test/**.vm_test.dart'
      ]),
      defaultOptions: _i5.BuilderOptions({
        'dart2js_args': ['--minify']
      }),
      defaultReleaseOptions: _i5.BuilderOptions({'compiler': 'dart2js'}),
      appliesBuilders: ['build_web_compilers|dart2js_archive_extractor']),
  _i1.applyPostProcess('build_modules|module_cleanup', _i2.moduleCleanup,
      defaultGenerateFor: const _i4.InputSet()),
  _i1.applyPostProcess(
      'build_web_compilers|dart_source_cleanup', _i3.dartSourceCleanup,
      defaultReleaseOptions: _i5.BuilderOptions({'enabled': true}),
      defaultGenerateFor: const _i4.InputSet()),
  _i1.applyPostProcess('build_web_compilers|dart2js_archive_extractor',
      _i3.dart2JsArchiveExtractor,
      defaultReleaseOptions: _i5.BuilderOptions({'filter_outputs': true}),
      defaultGenerateFor: const _i4.InputSet())
];
main(List<String> args, [_i6.SendPort sendPort]) async {
  var result = await _i7.run(args, _builders);
  sendPort?.send(result);
}
jakemac53 commented 5 years ago

So the reason I think this would happen is if you have duplicate sources across multiple targets.

I believe that you could resolve this issue by editing your default target in your build.yaml from the swlegion such that it excluded all sources from other targets.

The experience here is admittedly extremely poor though, i would not recommend using multiple targets unless you absolutely have to. They also mix very poorly with aggregate builders that use the magic placeholder files for instance.

matanlurey commented 5 years ago

Thanks! I admit the swlegion/build.yaml is more complicated than it should be, mostly because I needed a way to make sure the built_value generators ran before my own (so dependency order was important here). Is there another way I could have done this?

matanlurey commented 5 years ago

Thanks! It works with:

targets:
  $default:
    sources:
      exclude:
        - lib/src/database/**.dart
        - lib/src/model/**.dart
        - lib/src/all_models.dart
        - test/entity/**.dart

... let me know if we should close this issue, though.

jakemac53 commented 5 years ago

I think the best option for that would probably be specifying required_inputs in your builder, which is a list of input extensions that must be built before your builder runs.

We also have runs_before, which allows you to specify builder keys explicitly, but we don't have a corresponding runs_after at this time (just because it hasn't been requested, and would be more difficult to use appropriately).

jakemac53 commented 5 years ago

And ya I think it makes sense to just leave this open with the new title. We probably won't have time to address it soon, but its a valid issue that we should eventually address.

matanlurey commented 5 years ago

Thanks! I was able to simplify my entire build.yaml down to:

builders:
  _aggregate_database:
    target: "_aggregate_database"
    import: tool/src/aggregate_database.dart
    builder_factories:
      - aggregateDatabase
    build_extensions:
      $lib$:
        - src/all_units.dart
        - src/all_upgrades.dart
        - src/all_weapons.dart
    build_to: source
    auto_apply: root_package
    required_inputs:
      - lib/src/model/**.g.dart
natebosch commented 5 years ago

This could fall under the idea of having a validation tool: https://github.com/dart-lang/build/issues/885