Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 3 additions & 31 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ ENV PUB_CACHE=/opt/dart/pub-cache \
PUB_ENVIRONMENT="dependabot" \
PATH="${PATH}:/opt/dart/dart-sdk/bin"

ARG DART_VERSION=2.16.2
ARG DART_VERSION=2.17.0
RUN DART_ARCH=${TARGETARCH} \
&& if [ "$TARGETARCH" = "amd64" ]; then DART_ARCH=x64; fi \
&& curl --connect-timeout 15 --retry 5 "https://storage.googleapis.com/dart-archive/channels/stable/release/${DART_VERSION}/sdk/dartsdk-linux-${DART_ARCH}-release.zip" > "/tmp/dart-sdk.zip" \
Expand All @@ -248,42 +248,13 @@ RUN DART_ARCH=${TARGETARCH} \
&& chmod -R o+rx "/opt/dart/dart-sdk" \
&& rm "/tmp/dart-sdk.zip" \
&& dart --version
# We pull the dependency_services from the dart-lang/pub repo as it is not
# exposed from the Dart SDK (yet...).
RUN git clone https://github.com/dart-lang/pub.git /opt/dart/pub \
&& git -C /opt/dart/pub checkout 6f20a94b074a1c2dc82d429fa04d365b4bcf65b6 \
&& dart pub global activate --source path /opt/dart/pub \
&& chmod -R o+r "/opt/dart/pub" \
&& chown -R dependabot:dependabot "$PUB_CACHE" \
&& chown -R dependabot:dependabot /opt/dart/pub

# Install Flutter
ARG FLUTTER_VERSION=2.10.3
RUN curl --connect-timeout 15 --retry 5 "https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_${FLUTTER_VERSION}-stable.tar.xz" > "/tmp/flutter.xz" \
&& tar xf "/tmp/flutter.xz" -C /opt/dart \
&& rm "/tmp/flutter.xz" \
&& chmod -R o+rx "/opt/dart/flutter" \
&& chown -R dependabot:dependabot "/opt/dart/flutter" \
# To reduce space usage we delete all of the flutter sdk except the few
# things needed for pub resolutions:
# * The version file
# * The flutter sdk packages.
&& find "/opt/dart/flutter" \
! -path '/opt/dart/flutter/version' \
! -path '/opt/dart/flutter/packages/*' \
! -path '/opt/dart/flutter/packages' \
! -path '/opt/dart/flutter/bin/cache/pkg/*' \
! -path /opt/dart/flutter/bin/cache/pkg \
! -path /opt/dart/flutter/bin/cache \
! -path /opt/dart/flutter/bin \
! -path /opt/dart/flutter \
-delete

COPY --chown=dependabot:dependabot LICENSE /home/dependabot
COPY --chown=dependabot:dependabot composer/helpers /opt/composer/helpers
COPY --chown=dependabot:dependabot bundler/helpers /opt/bundler/helpers
COPY --chown=dependabot:dependabot go_modules/helpers /opt/go_modules/helpers
COPY --chown=dependabot:dependabot hex/helpers /opt/hex/helpers
COPY --chown=dependabot:dependabot pub/helpers /opt/pub/helpers
COPY --chown=dependabot:dependabot npm_and_yarn/helpers /opt/npm_and_yarn/helpers
COPY --chown=dependabot:dependabot python/helpers /opt/python/helpers
COPY --chown=dependabot:dependabot terraform/helpers /opt/terraform/helpers
Expand All @@ -300,6 +271,7 @@ RUN bash /opt/composer/helpers/v2/build
RUN bash /opt/go_modules/helpers/build
RUN bash /opt/hex/helpers/build
RUN bash /opt/npm_and_yarn/helpers/build
RUN bash /opt/pub/helpers/build
RUN bash /opt/python/helpers/build
RUN bash /opt/terraform/helpers/build

Expand Down
2 changes: 2 additions & 0 deletions pub/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@
/tmp
/dependabot-*.gem
Gemfile.lock
.dart_tool/
.packages
28 changes: 23 additions & 5 deletions pub/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ Dart (pub) support for [`dependabot-core`][core-repo].
- If the version found is ignored (by dependabot config) no update will happen (even if, an earlier version could be used)
- Limited metadata support (just retrieves the repository link).
- No support for auhtentication of private package repositories (mostly a configuration issue).
- Only stable versions of Dart and Flutter supported.
- `updated_dependencies_after_full_unlock` only allows updating to a later version, if the latest version that is mutually compatible with other dependencies is the latest version of the said package. This is a dependabot limitation.

### Running locally
Expand All @@ -36,6 +35,8 @@ Dart (pub) support for [`dependabot-core`][core-repo].
The `dart pub` repo offers an experimental dependency services interface which
allows checking for available updates.

It is implemented as helpers/bin/dependency_services.dart, that is mainly a wrapper around the implementation in the [pub client](https://github.com/dart-lang/pub).

#### List Dependencies

```js
Expand Down Expand Up @@ -83,16 +84,16 @@ allows checking for available updates.
"latest": "<version>",

// In the following possible upgrades are listed for different
//
//
// The constraints are given in three versions, according to different
// strategies for updating constraint to allow the new version of a
// strategies for updating constraint to allow the new version of a
// package:
//
// * "constraintBumped": always update the constraint lower bound to match
// the new version.
// * "constraintBumpedIfNeeded": leave the constraint if the original
// constraint allows the new version.
// * "constraintWidened": extend only the upper bound to include the new
// * "constraintWidened": extend only the upper bound to include the new
// version.

// If it is possible to upgrade the current version without making any
Expand Down Expand Up @@ -228,4 +229,21 @@ the url of the repository.
"type": "git" || "hosted" || "path" || "sdk", // Name of the source.
... // Other keys are free form json information about the dependency
}
```
```
## Detection of Flutter and Dart SDK versions.

`dependency_services` should be run in the context of the right Flutter and
Dart SDK versions as these will affect package resolution.

The pub dependabot integration supports the flutter releases on the `stable` and
`beta`
[channel](https://github.com/flutter/flutter/wiki/Flutter-build-release-channels).
Each Flutter release comes with a matching Dart release.

The `helpers/bin/infer_sdk_versions.dart` script will parse the root pubspec, and
try to determine the right release based on the SDK constraints and the list of
available releases:

* The latest stable release that matches the SDK constraints will be chosen
* If there is no stable release it will choose the newest beta that matches the
SDK constraints.
78 changes: 78 additions & 0 deletions pub/helpers/bin/dependency_services.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/// Support for automated upgrades.
library dependency_services;

import 'dart:async';

import 'package:args/args.dart';
import 'package:args/command_runner.dart';
import 'package:pub/src/command.dart';
import 'package:pub/src/command/dependency_services.dart';
import 'package:pub/src/exit_codes.dart' as exit_codes;
import 'package:pub/src/io.dart';
import 'package:pub/src/log.dart' as log;

class _DependencyServicesCommandRunner extends CommandRunner<int>
implements PubTopLevel {
@override
String? get directory => argResults['directory'];

@override
bool get captureStackChains => argResults['verbose'];

@override
bool get trace => argResults['verbose'];

ArgResults? _argResults;

/// The top-level options parsed by the command runner.
@override
ArgResults get argResults {
final a = _argResults;
if (a == null) {
throw StateError(
'argResults cannot be used before Command.run is called.');
}
return a;
}

_DependencyServicesCommandRunner()
: super('dependency_services', 'Support for automatic upgrades',
usageLineLength: lineLength) {
argParser.addFlag('verbose',
abbr: 'v', negatable: false, help: 'Shortcut for "--verbosity=all".');
argParser.addOption(
'directory',
abbr: 'C',
help: 'Run the subcommand in the directory<dir>.',
defaultsTo: '.',
valueHelp: 'dir',
);

addCommand(DependencyServicesListCommand());
addCommand(DependencyServicesReportCommand());
addCommand(DependencyServicesApplyCommand());
}

@override
Future<int> run(Iterable<String> args) async {
try {
_argResults = parse(args);
return await runCommand(argResults) ?? exit_codes.SUCCESS;
} on UsageException catch (error) {
log.exception(error);
return exit_codes.USAGE;
}
}

@override
void printUsage() {
log.message(usage);
}

@override
log.Verbosity get verbosity => log.Verbosity.normal;
}

Future<void> main(List<String> arguments) async {
await flushThenExit(await _DependencyServicesCommandRunner().run(arguments));
}
165 changes: 165 additions & 0 deletions pub/helpers/bin/infer_sdk_versions.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import 'dart:convert';
import 'dart:io';

import 'package:args/args.dart';
import 'package:collection/collection.dart';
import 'package:http/http.dart';
import 'package:http/retry.dart';
import 'package:yaml/yaml.dart';
import 'package:pub_semver/pub_semver.dart';
import 'package:path/path.dart' as p;

Never fail(String message) {
stderr.writeln(message);
exit(-1);
}

final client = RetryClient(Client());

ArgResults parseArgs(List<String> args) {
final argParser = ArgParser()
..addOption(
'directory',
abbr: 'C',
defaultsTo: '.',
help: 'The directory containing the pubspec.yaml of the package.',
)
..addOption('flutter-releases-url',
help:
'The url to retrieve the list of available flutter releases from.')
..addFlag('help', help: 'Display the usage message.');
final results;
try {
results = argParser.parse(args);
if (results['help'] as bool) {
stdout.writeln(
'Infers the newest available flutter sdk to use for a package.');
stdout.writeln(argParser.usage);
exit(0);
}
return results;
} on FormatException catch (e) {
stderr.writeln(e.message);
stderr.writeln(argParser.usage);
exit(-1);
}
}

Map<String, VersionConstraint> parseSdkConstraints(dynamic pubspec) {
final dartConstraint =
VersionConstraint.parse(pubspec['environment']?['sdk'] ?? 'any');
final flutterConstraint =
VersionConstraint.parse(pubspec['environment']?['flutter'] ?? 'any');
return {
'dart': dartConstraint,
'flutter': flutterConstraint,
};
}

Future<void> main(List<String> args) async {
try {
final argResults = parseArgs(args);
var url = argResults['flutter-releases-url'];
if (url == null || url.isEmpty) {
url = flutterReleasesUrl;
}
final flutterReleases = await retrieveFlutterReleases(url);

final pubspecPath = p.join(argResults['directory'], 'pubspec.yaml');
final pubspec = loadYaml(File(pubspecPath).readAsStringSync(),
sourceUrl: Uri.file(pubspecPath));
Comment on lines +69 to +70
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inferring is a good start, but dependabot should allow pointing to an exact git-ref (tag, sha1).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm - do you have an idea for a good place to put this configuration?

What are scenarios where you would need this?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Scenario: We use flutterw to pin the flutter version. Others use fvm or custom solutions.

Tree options pop into my mind:

  • Lookup: dependabot checks the version of those pinning solutions (i.e. check git submodule sha1 or a configuration file)
  • Add a flutter version to .github/dependabot.yaml
  • Define a new dependabot.flutter_version property to pubspec.yaml of each package and read it

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc @jonasfj what do you think?

Copy link
Copy Markdown
Contributor

@jonasfj jonasfj Jun 10, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that it sufficient that we allow pinning the Flutter SDK constraint in environment.flutter.

Generally, the dart pub client will ignore the upper-bound in environment.flutter, as a result of a decision made in Flutter 2 that Flutter wouldn't break backwards compatibility.

But in dependabot, I think we made it respect the upper-bound, thus, the environment.flutter constraint can be used to pin a Flutter version. You can't pin to arbitrary sha or tag, but you can pin to stable and beta releases of Flutter.

My two cents is that this is a reasonable compromise.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using the lower bound of environment.flutter would work for us. But it could prevent dependabot from adding the latest versions.

Using the upper bound wouldn't be practical. We'd have to change it every time we upgrade in all packages of our mono repo.


final bestFlutterRelease =
inferBestFlutterRelease(parseSdkConstraints(pubspec), flutterReleases);
if (bestFlutterRelease == null) {
fail(
'No flutter release matching sdk constraints.',
);
}
stdout.writeln(JsonEncoder.withIndent(' ').convert({
'flutter': bestFlutterRelease.flutterVersion.toString(),
'dart': bestFlutterRelease.dartVersion.toString(),
'channel': {
Channel.stable: 'stable',
Channel.beta: 'beta',
Channel.dev: 'dev'
}[bestFlutterRelease.channel],
}));
} on FormatException catch (e) {
fail(e.message);
} finally {
client.close();
}
}

String get flutterReleasesUrl =>
'https://storage.googleapis.com/flutter_infra_release/releases/releases_linux.json';

// Retrieves all released versions of Flutter.
Future<List<FlutterRelease>> retrieveFlutterReleases(String url) async {
final response = await client.get(Uri.parse(url));
final decoded = jsonDecode(response.body);
if (decoded is! Map) throw FormatException('Bad response - should be a Map');
final releases = decoded['releases'];
if (releases is! List)
throw FormatException('Bad response - releases should be a list.');
final result = <FlutterRelease>[];
for (final release in releases) {
final channel = {
'beta': Channel.beta,
'stable': Channel.stable,
'dev': Channel.dev
}[release['channel']];
if (channel == null) throw FormatException('Release with bad channel');
final dartVersion = release['dart_sdk_version'];
// Some releases don't have an associated dart version, ignore.
if (dartVersion is! String) continue;
final flutterVersion = release['version'];
if (flutterVersion is! String) throw FormatException('Not a string');
result.add(FlutterRelease(
flutterVersion: Version.parse(flutterVersion),
dartVersion: Version.parse(dartVersion.split(' ').first),
channel: channel,
));
}
return result
// Sort releases by channel and version.
.sorted((a, b) {
final compareChannels = b.channel.index - a.channel.index;
if (compareChannels != 0) return compareChannels;
return a.flutterVersion.compareTo(b.flutterVersion);
})
// Newest first.
.reversed
.toList();
}

/// The "best" Flutter release for a given set of constraints is the first one
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does "first" mean: oldest or newest Flutter version?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is the first in the list passed to this function.

I tried to rephrase a bit.

/// in [flutterReleases] that matches both the flutter and dart constraint.
FlutterRelease? inferBestFlutterRelease(
Map<String, VersionConstraint> sdkConstraints,
List<FlutterRelease> flutterReleases) {
return flutterReleases.firstWhereOrNull((release) =>
(sdkConstraints['flutter'] ?? VersionConstraint.any)
.allows(release.flutterVersion) &&
(sdkConstraints['dart'] ?? VersionConstraint.any)
.allows(release.dartVersion));
}

enum Channel {
stable,
beta,
dev,
}

/// A version of the Flutter SDK and its related Dart SDK.
class FlutterRelease {
final Version flutterVersion;
final Version dartVersion;
final Channel channel;
FlutterRelease({
required this.flutterVersion,
required this.dartVersion,
required this.channel,
});
}
17 changes: 17 additions & 0 deletions pub/helpers/build
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/bin/bash

# Precompiles the helper scripts for the pub integration.

set -e

if [ -z "$DEPENDABOT_NATIVE_HELPERS_PATH" ]; then
echo "Unable to build, DEPENDABOT_NATIVE_HELPERS_PATH is not set"
exit 1
fi

# Retrieve the dependencies
dart pub get -C "$DEPENDABOT_NATIVE_HELPERS_PATH/pub/helpers"

# Compile the helpers
dart compile exe "$DEPENDABOT_NATIVE_HELPERS_PATH/pub/helpers/bin/dependency_services.dart" -o "$DEPENDABOT_NATIVE_HELPERS_PATH/pub/dependency_services"
dart compile exe "$DEPENDABOT_NATIVE_HELPERS_PATH/pub/helpers/bin/infer_sdk_versions.dart" -o "$DEPENDABOT_NATIVE_HELPERS_PATH/pub/infer_sdk_versions"
Loading