@@ -73,7 +73,10 @@ CommandGroup[] getCommands() @safe pure nothrow
73
73
new ListOverridesCommand,
74
74
new CleanCachesCommand,
75
75
new ConvertCommand,
76
- )
76
+ // This is index management but those commands are hidden
77
+ new IndexBuildCommand,
78
+ new IndexFromRegistryCommand,
79
+ ),
77
80
];
78
81
}
79
82
@@ -273,7 +276,8 @@ unittest {
273
276
assert (handler.commandNames == [" init" , " run" , " build" , " test" , " lint" , " generate" ,
274
277
" describe" , " clean" , " dustmite" , " fetch" , " add" , " remove" ,
275
278
" upgrade" , " add-path" , " remove-path" , " add-local" , " remove-local" , " list" , " search" ,
276
- " add-override" , " remove-override" , " list-overrides" , " clean-caches" , " convert" ]);
279
+ " add-override" , " remove-override" , " list-overrides" , " clean-caches" , " convert" ,
280
+ " index-build" , " index-fromregistry" ]);
277
281
}
278
282
279
283
// / It sets the cwd as root_path by default
@@ -2983,6 +2987,255 @@ class ConvertCommand : Command {
2983
2987
}
2984
2988
2985
2989
2990
+ /* *****************************************************************************/
2991
+ /* Index management
2992
+ /******************************************************************************/
2993
+
2994
+ public class IndexBuildCommand : Command {
2995
+ import dub.index.bitbucket;
2996
+ import dub.index.data;
2997
+ import dub.index.github;
2998
+ import dub.index.gitlab;
2999
+ import dub.index.utils;
3000
+ import std.random ;
3001
+ import std.range ;
3002
+
3003
+ // / Index file to use
3004
+ private string index = " index.yaml" ;
3005
+ // / Filename to write to
3006
+ private string output = " index-build-result" ;
3007
+ // / Packages to filter in - assume all if empty
3008
+ private string [] include;
3009
+ // / Packages to filter out
3010
+ private string [] exclude;
3011
+ // / Bearer token to use to authenticate requests
3012
+ private string githubToken, gitlabToken, bitbucketToken;
3013
+ // / Kind of packages to include (default: all kinds)
3014
+ private string [] kind;
3015
+ // / Whether to force the iteration of tags or not
3016
+ // / This needs to be used if some tags need to be reprocessed
3017
+ private bool force_tags;
3018
+ // / Force the package to be entirely reprocessed. Imply `--force-tags`.
3019
+ private bool force;
3020
+ // / Whether to use a randomized sample of packages
3021
+ private bool random;
3022
+ // / The number of packages to update
3023
+ private uint maxUpdates = uint .max;
3024
+ // / Source and target index to use, mutually exclusive with `random`
3025
+ private uint fromIdx = 0 , toIdx = uint .max;
3026
+
3027
+ this () @safe pure nothrow
3028
+ {
3029
+ this .name = " index-build" ;
3030
+ this .description = " Generate the rich index from the index.yaml file" ;
3031
+ this .helpText = [ " This command is for internal use only. Do not use it." ];
3032
+ this .hidden = true ;
3033
+ }
3034
+
3035
+ override void prepare (scope CommandArgs args) {
3036
+ args.getopt(" bitbucket-token" , &this .bitbucketToken, [" Bearer token to use when issuing Bitbucket requests" ]);
3037
+ args.getopt(" github-token" , &this .githubToken, [" Bearer token to use when issuing Github requests" ]);
3038
+ args.getopt(" gitlab-token" , &this .gitlabToken, [" Bearer token to use when issuing GitLab requests" ]);
3039
+ args.getopt(" output" , &this .output, [" Where to output the data (path to a folder)" ]);
3040
+ args.getopt(" index" , &this .index, [" Index file to use - default to 'index.yaml'" ]);
3041
+ args.getopt(" include" , &this .include, [" Which packages to filter in - if not, assume all" ]);
3042
+ args.getopt(" exclude" , &this .exclude, [" Which packages to filter out - if not, assume none" ]);
3043
+ args.getopt(" kind" , &this .kind, [" Kind of packages to include (github, gitlab, bitbucket). Default: all" ]);
3044
+ args.getopt(" force" , &this .force, [" Force Dub to reprocess packages even if it has cache informations" ]);
3045
+ args.getopt(" force-tags" , &this .force_tags,
3046
+ [" Force Dub to re-list tags, but do not reload the recipe if the commit hasn't changed." ]);
3047
+ args.getopt(" random" , &this .random, [" Randomize the order in which packages are processed" ]);
3048
+ args.getopt(" max-updates" , &this .maxUpdates, [" Maximum number of packages to process" ]);
3049
+ args.getopt(" from" , &this .fromIdx, [" Index to seek to before iterating the list of packages (default: 0)" ]);
3050
+ args.getopt(" to" , &this .toIdx, [" Index to stop at when iterating the list of packages (default: end of list)" ]);
3051
+
3052
+ enforce(this .fromIdx <= this .toIdx, " Cannot have source index (`--from`) be past end index (`--to`)" );
3053
+ enforce(this .fromIdx == 0 || ! this .random,
3054
+ " Cannot specify source index (`--from`) for random sampling (`--random`)" );
3055
+ enforce(this .toIdx == uint .max || ! this .random,
3056
+ " Cannot specify end index (`--to`) for random sampling (`--random`)" );
3057
+ }
3058
+
3059
+ override int execute (Dub dub, string [] free_args, string [] app_args)
3060
+ {
3061
+ import dub.index.client : RepositoryClient;
3062
+ import dub.index.data;
3063
+ import dub.internal.configy.easy;
3064
+ static import std.file ;
3065
+ import std.typecons ;
3066
+
3067
+ enforceUsage(free_args.length == 0 , " Expected no free argument." );
3068
+ enforceUsage(app_args.length == 0 , " Expected zero application arguments." );
3069
+
3070
+ const isUpdate = std.file.exists (this .output);
3071
+ if (isUpdate)
3072
+ enforce(std.file.isDir (this .output), this .output ~ " : is not a directory" );
3073
+ else
3074
+ std.file.mkdirRecurse (this .output);
3075
+
3076
+ auto indexN = parseConfigFileSimple! PackageList(this .index);
3077
+ if (indexN.isNull()) return 1 ;
3078
+ auto indexC = indexN.get ();
3079
+ logInfoNoTag(" Found %s packages in the index file" , indexC.packages.length);
3080
+
3081
+ const NativePath outputPath = NativePath(this .output);
3082
+ size_t processed, updated, notsupported;
3083
+ string [] included, excluded, errored;
3084
+ scope gh = new GithubClient(this .githubToken);
3085
+ scope gl = new GitLabClient(this .gitlabToken);
3086
+ scope bb = new BitbucketClient(this .bitbucketToken);
3087
+
3088
+ void update (scope RepositoryClient client, in PackageEntry pkg) {
3089
+ const target = getPackageDescriptionPath(outputPath, PackageName(pkg.name.value));
3090
+ ensureDirectory(target.parentPath());
3091
+ const targetStr = target.toNativeString();
3092
+ auto previous = ! this .force && std.file.exists (targetStr) ?
3093
+ parseConfigFileSimple! (IndexedPackage! 0 )(targetStr, StrictMode.Ignore) :
3094
+ Nullable! (IndexedPackage! 0 ).init;
3095
+ if (this .force_tags && ! previous.isNull())
3096
+ previous.get ().cache = CacheInfo.init;
3097
+ auto res = updateDescription(client, pkg, previous);
3098
+ if (previous.isNull() || previous.get () != res) {
3099
+ std.file.write (targetStr, res.serializeToJsonString());
3100
+ ++ updated;
3101
+ }
3102
+ }
3103
+
3104
+ // Update a single package - this code is in its own function as it
3105
+ // is wrapped in a try-catch in the `foreach` to process as many packages
3106
+ // as possible
3107
+ void updatePackageIndex (in PackageEntry pkg) {
3108
+ logInfo(" [%s] Processing included package" , pkg.name.value);
3109
+ switch (pkg.source.kind) {
3110
+ case ` github` :
3111
+ scope client = gh.new Repository(pkg.source.owner, pkg.source.project);
3112
+ update(client, pkg);
3113
+ break ;
3114
+ case ` gitlab` :
3115
+ scope client = gl.new Project(pkg.source.owner, pkg.source.project);
3116
+ update(client, pkg);
3117
+ break ;
3118
+ case ` bitbucket` :
3119
+ scope client = bb.new Repository(pkg.source.owner, pkg.source.project);
3120
+ update(client, pkg);
3121
+ break ;
3122
+ default :
3123
+ throw new Exception (" Package kind not supported: " ~ pkg.source.kind);
3124
+ }
3125
+ }
3126
+
3127
+ int processEntry (size_t idx, ref PackageEntry pkg) {
3128
+ if (updated >= this .maxUpdates) return 1 ;
3129
+ if (this .include.length && ! this .include.canFind(pkg.name.value))
3130
+ return 0 ;
3131
+ if (this .exclude.canFind(pkg.name.value)) {
3132
+ excluded ~= pkg.name.value;
3133
+ return 0 ;
3134
+ }
3135
+ if (this .kind.length && ! this .kind.canFind(pkg.source.kind))
3136
+ return 0 ;
3137
+
3138
+ ++ processed;
3139
+ try
3140
+ updatePackageIndex(pkg);
3141
+ catch (Exception exc) {
3142
+ errored ~= pkg.name.value;
3143
+ // If we get a 404 here, it might be a dead package
3144
+ logError(" [%s] Could not build index for package: %s" ,
3145
+ pkg.name.value, exc.message());
3146
+ }
3147
+
3148
+ if (this .include.length) {
3149
+ included ~= pkg.name.value;
3150
+ if (included.length % 10 == 0 ) {
3151
+ const rl = gh.getRateLimit();
3152
+ logDebug(" Requests still available: %s/%s" , rl.remaining, rl.limit);
3153
+ }
3154
+ }
3155
+ else if (idx % 10 == 0 ) {
3156
+ const rl = gh.getRateLimit();
3157
+ logDebug(" Requests still available: %s/%s" , rl.remaining, rl.limit);
3158
+ }
3159
+ return 0 ;
3160
+ }
3161
+
3162
+ if (this .random) { // Can't use `std.random : choose` because bugs
3163
+ foreach (idx, pkg; indexC.packages.randomCover().enumerate)
3164
+ if (processEntry(idx, pkg))
3165
+ break ;
3166
+ } else {
3167
+ const startIdx = min(this .fromIdx, indexC.packages.length);
3168
+ const endIdx = min(this .toIdx, indexC.packages.length);
3169
+ foreach (idx, pkg; indexC.packages[startIdx .. endIdx])
3170
+ if (processEntry(idx, pkg))
3171
+ break ;
3172
+ }
3173
+
3174
+ logInfoNoTag(" Updated %s packages out of %s processed (%s excluded, %s errors, %s not supported)" ,
3175
+ updated, processed, excluded.length, errored.length, notsupported);
3176
+ if (this .include.length && included != this .include)
3177
+ logWarn(" Not all explicitly-included packages have been processed!" );
3178
+ if (errored.length)
3179
+ logWarn(" The following packages errored out:\n %(\t - %s\n %)" , errored);
3180
+ if (! this .kind.length || this .kind.canFind(` github` )) {
3181
+ const rl = gh.getRateLimit();
3182
+ logInfoNoTag(" Github requests still available: %s/%s" , rl.remaining, rl.limit);
3183
+ }
3184
+ return 0 ;
3185
+ }
3186
+ }
3187
+
3188
+ public class IndexFromRegistryCommand : Command {
3189
+ // / Filename to write to
3190
+ private string output = " index.yaml" ;
3191
+ // / Bypass cache, always query the registry
3192
+ private bool force;
3193
+
3194
+ this () @safe pure nothrow
3195
+ {
3196
+ this .name = " index-fromregistry" ;
3197
+ this .description = " Generate the index.yaml file from the remote registry" ;
3198
+ this .helpText = [ " This command is for internal use only. Do not use it." ];
3199
+ this .hidden = true ;
3200
+ }
3201
+
3202
+ override void prepare (scope CommandArgs args) {
3203
+ args.getopt(" O" , &this .output, [" Where to output the data ('-' is supported)" ]);
3204
+ args.getopt(" f" , &this .force, [" Bypass the cache and always query the registry" ]);
3205
+ }
3206
+
3207
+ override int execute (Dub dub, string [] free_args, string [] app_args)
3208
+ {
3209
+ import dub.internal.vibecompat.inet.url;
3210
+ import dub.packagesuppliers.registry;
3211
+ import std.format ;
3212
+
3213
+ enforceUsage(free_args.length == 0 , " Expected zero arguments." );
3214
+ enforceUsage(app_args.length == 0 , " Expected zero application arguments." );
3215
+
3216
+ scope registry = new RegistryPackageSupplier(URL (defaultRegistryURLs[1 ]));
3217
+ scope allPkgs = registry.getPackageDump(this .force);
3218
+ writeln(" Found " , allPkgs.array.length, " packages" );
3219
+ scope output = this .output == " -" ? stdout : File (this .output, " w+" );
3220
+ scope writer = output.lockingTextWriter();
3221
+ writer.formattedWrite(" packages:\n " );
3222
+ foreach (pkg; allPkgs.array) {
3223
+ writer.formattedWrite(` %s:
3224
+ source:
3225
+ kind: %s
3226
+ owner: %s
3227
+ project: %s
3228
+ ` ,
3229
+ pkg[" name" ].opt! string , pkg[" repository" ][" kind" ].opt! string ,
3230
+ pkg[" repository" ][" owner" ].opt! string ,
3231
+ pkg[" repository" ][" project" ].opt! string );
3232
+ }
3233
+
3234
+ return 0 ;
3235
+ }
3236
+ }
3237
+
3238
+
2986
3239
/* *****************************************************************************/
2987
3240
/* HELP */
2988
3241
/* *****************************************************************************/
0 commit comments